Merge "Require permission to unlock keyguard" into main
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index 1ce8f9f..bdec8b9 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -59,22 +59,25 @@
 and `"bluetooth"`.
 
 Device configuration is used to configure the uinput device. The `type` field provides a `UI_SET_*`
-control code, and data is a vector of control values to be sent to the uinput device, which depends
-on the control code.
+control code as an integer value or a string label (e.g. `"UI_SET_EVBIT"`), and data is a vector of
+control values to be sent to the uinput device, which depends on the control code.
 
-| Field         |     Type      | Description                |
-|:-------------:|:-------------:|:-------------------------- |
-| `type`        | integer       | `UI_SET_` control type     |
-| `data`        | integer array | control values             |
+| Field         |         Type          | Description            |
+|:-------------:|:---------------------:|:-----------------------|
+| `type`        |    integer\|string    | `UI_SET_` control type |
+| `data`        | integer\|string array | control values         |
+
+Due to the sequential nature in which this is parsed, the `type` field must be specified before
+the `data` field in this JSON Object.
 
 `ff_effects_max` must be provided if `UI_SET_FFBIT` is used in `configuration`.
 
 `abs_info` fields are provided to set the device axes information. It is an array of below objects:
 
-| Field         | Type          | Description                |
-|:-------------:|:-------------:|:-------------------------- |
-| `code`        | integer       | Axis code                  |
-| `info`        | object        | Axis information object    |
+| Field         |      Type       | Description             |
+|:-------------:|:---------------:|:------------------------|
+| `code`        | integer\|string | Axis code or label      |
+| `info`        |     object      | Axis information object |
 
 The axis information object is defined as below, with the fields having the same meaning as those
 Linux's [`struct input_absinfo`][struct input_absinfo]:
@@ -99,16 +102,17 @@
   "pid": 0x2c42,
   "bus": "usb",
   "configuration":[
-        {"type":100, "data":[1, 21]},         // UI_SET_EVBIT : EV_KEY and EV_FF
-        {"type":101, "data":[11, 2, 3, 4]},   // UI_SET_KEYBIT : KEY_0 KEY_1 KEY_2 KEY_3
-        {"type":107, "data":[80]}             // UI_SET_FFBIT : FF_RUMBLE
+        {"type":"UI_SET_EVBIT", "data":["EV_KEY", "EV_FF"]},
+        {"type":"UI_SET_KEYBIT", "data":["KEY_0", "KEY_1", "KEY_2", "KEY_3"]},
+        {"type":"UI_SET_ABSBIT", "data":["ABS_Y", "ABS_WHEEL"]},
+        {"type":"UI_SET_FFBIT", "data":["FF_RUMBLE"]}
   ],
   "ff_effects_max" : 1,
   "abs_info": [
-        {"code":1, "info": {"value":20, "minimum":-255,
+        {"code":"ABS_Y", "info": {"value":20, "minimum":-255,
                             "maximum":255, "fuzz":0, "flat":0, "resolution":1}
         },
-        {"code":8, "info": {"value":-50, "minimum":-255,
+        {"code":"ABS_WHEEL", "info": {"value":-50, "minimum":-255,
                             "maximum":255, "fuzz":0, "flat":0, "resolution":1}
         }
   ]
@@ -157,11 +161,11 @@
 
 Send an array of uinput event packets to the uinput device
 
-| Field         | Type          | Description                |
-|:-------------:|:-------------:|:-------------------------- |
-| `id`          | integer       | Device ID                  |
-| `command`     | string        | Must be set to "inject"    |
-| `events`      | integer array | events to inject           |
+| Field         |         Type          | Description                |
+|:-------------:|:---------------------:|:-------------------------- |
+| `id`          |        integer        | Device ID                  |
+| `command`     |        string         | Must be set to "inject"    |
+| `events`      | integer\|string array | events to inject           |
 
 The `events` parameter is an array of integers in sets of three: a type, an axis code, and an axis
 value, like you'd find in Linux's `struct input_event`. For example, sending presses of the 0 and 1
@@ -171,14 +175,14 @@
 {
   "id": 1,
   "command": "inject",
-  "events": [0x01, 0xb,  0x1,   // EV_KEY, KEY_0, DOWN
-             0x00, 0x00, 0x00,  // EV_SYN, SYN_REPORT, 0
-             0x01, 0x0b, 0x00,  // EV_KEY, KEY_0, UP
-             0x00, 0x00, 0x00,  // EV_SYN, SYN_REPORT, 0
-             0x01, 0x2,  0x1,   // EV_KEY, KEY_1, DOWN
-             0x00, 0x00, 0x01,  // EV_SYN, SYN_REPORT, 0
-             0x01, 0x02, 0x00,  // EV_KEY, KEY_1, UP
-             0x00, 0x00, 0x01   // EV_SYN, SYN_REPORT, 0
+  "events": ["EV_KEY", "KEY_0", 1,
+             "EV_SYN", "SYN_REPORT", 0,
+             "EV_KEY", "KEY_0", 0,
+             "EV_SYN", "SYN_REPORT", 0,
+             "EV_KEY", "KEY_1", 1,
+             "EV_SYN", "SYN_REPORT", 0,
+             "EV_KEY", "KEY_1", 0,
+             "EV_SYN", "SYN_REPORT", 0
             ]
 }
 ```
diff --git a/cmds/uinput/jni/Android.bp b/cmds/uinput/jni/Android.bp
index c56adc3..558bcc5 100644
--- a/cmds/uinput/jni/Android.bp
+++ b/cmds/uinput/jni/Android.bp
@@ -21,6 +21,7 @@
         "libbase",
         "libbinder",
         "liblog",
+        "libinput",
         "libnativehelper",
     ],
 
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index 3f4163d..7659054 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -16,12 +16,24 @@
 
 #define LOG_TAG "UinputCommandDevice"
 
-#include <linux/uinput.h>
+#include "com_android_commands_uinput_Device.h"
 
+#include <android-base/stringprintf.h>
+#include <android/looper.h>
+#include <android_os_Parcel.h>
 #include <fcntl.h>
+#include <input/InputEventLabels.h>
 #include <inttypes.h>
+#include <jni.h>
+#include <linux/uinput.h>
+#include <log/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <time.h>
 #include <unistd.h>
+
 #include <algorithm>
 #include <array>
 #include <cstdio>
@@ -30,19 +42,6 @@
 #include <memory>
 #include <vector>
 
-#include <android/looper.h>
-#include <android_os_Parcel.h>
-#include <jni.h>
-#include <log/log.h>
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
-#include <nativehelper/ScopedUtfChars.h>
-
-#include <android-base/stringprintf.h>
-
-#include "com_android_commands_uinput_Device.h"
-
 namespace android {
 namespace uinput {
 
@@ -307,6 +306,21 @@
     ::ioctl(static_cast<int>(handle), UI_ABS_SETUP, &absSetup);
 }
 
+static jint getEvdevEventTypeByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) {
+    ScopedUtfChars label(env, rawLabel);
+    return InputEventLookup::getLinuxEvdevEventTypeByLabel(label.c_str()).value_or(-1);
+}
+
+static jint getEvdevEventCodeByLabel(JNIEnv* env, jclass /* clazz */, jint type, jstring rawLabel) {
+    ScopedUtfChars label(env, rawLabel);
+    return InputEventLookup::getLinuxEvdevEventCodeByLabel(type, label.c_str()).value_or(-1);
+}
+
+static jint getEvdevInputPropByLabel(JNIEnv* env, jclass /* clazz */, jstring rawLabel) {
+    ScopedUtfChars label(env, rawLabel);
+    return InputEventLookup::getLinuxEvdevInputPropByLabel(label.c_str()).value_or(-1);
+}
+
 static JNINativeMethod sMethods[] = {
         {"nativeOpenUinputDevice",
          "(Ljava/lang/String;IIIIILjava/lang/String;"
@@ -316,6 +330,12 @@
         {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
         {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
         {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
+        {"nativeGetEvdevEventTypeByLabel", "(Ljava/lang/String;)I",
+         reinterpret_cast<void*>(getEvdevEventTypeByLabel)},
+        {"nativeGetEvdevEventCodeByLabel", "(ILjava/lang/String;)I",
+         reinterpret_cast<void*>(getEvdevEventCodeByLabel)},
+        {"nativeGetEvdevInputPropByLabel", "(Ljava/lang/String;)I",
+         reinterpret_cast<void*>(getEvdevInputPropByLabel)},
 };
 
 int register_com_android_commands_uinput_Device(JNIEnv* env) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 732b33d..6458eef 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -66,6 +66,9 @@
     private static native void nativeInjectEvent(long ptr, int type, int code, int value);
     private static native void nativeConfigure(int handle, int code, int[] configs);
     private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
+    private static native int nativeGetEvdevEventTypeByLabel(String label);
+    private static native int nativeGetEvdevEventCodeByLabel(int type, String label);
+    private static native int nativeGetEvdevInputPropByLabel(String label);
 
     public Device(int id, String name, int vid, int pid, int bus,
             SparseArray<int[]> configuration, int ffEffectsMax,
@@ -234,4 +237,32 @@
             msg.sendToTarget();
         }
     }
+
+    static int getEvdevEventTypeByLabel(String label) {
+        final var type = nativeGetEvdevEventTypeByLabel(label);
+        if (type < 0) {
+            throw new IllegalArgumentException(
+                    "Failed to get evdev event type from label: " + label);
+        }
+        return type;
+    }
+
+    static int getEvdevEventCodeByLabel(int type, String label) {
+        final var code = nativeGetEvdevEventCodeByLabel(type, label);
+        if (code < 0) {
+            throw new IllegalArgumentException(
+                    "Failed to get evdev event code for type " + type + " from label: " + label);
+        }
+        return code;
+
+    }
+
+    static int getEvdevInputPropByLabel(String label) {
+        final var prop = nativeGetEvdevInputPropByLabel(label);
+        if (prop < 0) {
+            throw new IllegalArgumentException(
+                    "Failed to get evdev input prop from label: " + label);
+        }
+        return prop;
+    }
 }
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 4b090f5..cddb407 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -25,6 +25,9 @@
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
 import java.util.stream.IntStream;
 
 import src.com.android.commands.uinput.InputAbsInfo;
@@ -39,7 +42,35 @@
     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;
+    private static final int EV_KEY = 0x01;
+    private static final int EV_REL = 0x02;
+    private static final int EV_ABS = 0x03;
+    private static final int EV_MSC = 0x04;
+    private static final int EV_SW = 0x05;
+    private static final int EV_LED = 0x11;
+    private static final int EV_SND = 0x12;
+    private static final int EV_FF = 0x15;
+
+    private enum UinputControlCode {
+        UI_SET_EVBIT("UI_SET_EVBIT", 100),
+        UI_SET_KEYBIT("UI_SET_KEYBIT", 101),
+        UI_SET_RELBIT("UI_SET_RELBIT", 102),
+        UI_SET_ABSBIT("UI_SET_ABSBIT", 103),
+        UI_SET_MSCBIT("UI_SET_MSCBIT", 104),
+        UI_SET_LEDBIT("UI_SET_LEDBIT", 105),
+        UI_SET_SNDBIT("UI_SET_SNDBIT", 106),
+        UI_SET_FFBIT("UI_SET_FFBIT", 107),
+        UI_SET_SWBIT("UI_SET_SWBIT", 109),
+        UI_SET_PROPBIT("UI_SET_PROPBIT", 110);
+
+        final String mName;
+        final int mValue;
+
+        UinputControlCode(String name, int value) {
+            this.mName = name;
+            this.mValue = value;
+        }
+    }
 
     // These constants come from "include/uapi/linux/input.h" in the kernel
     enum Bus {
@@ -257,8 +288,8 @@
                                 eb.setBus(readBus());
                                 break;
                             case "events":
-                                int[] injections = readIntList().stream()
-                                            .mapToInt(Integer::intValue).toArray();
+                                int[] injections = readInjectedEvents().stream()
+                                        .mapToInt(Integer::intValue).toArray();
                                 eb.setInjections(injections);
                                 break;
                             case "configuration":
@@ -293,12 +324,17 @@
             return e;
         }
 
-        private ArrayList<Integer> readIntList() throws IOException {
-            ArrayList<Integer> data = new ArrayList<Integer>();
+        private ArrayList<Integer> readInjectedEvents() throws IOException {
+            ArrayList<Integer> data = new ArrayList<>();
             try {
                 mReader.beginArray();
                 while (mReader.hasNext()) {
-                    data.add(Integer.decode(mReader.nextString()));
+                    // Read events in groups of three, because we expect an event type, event code,
+                    // and event value.
+                    final int type = readEvdevEventType();
+                    data.add(type);
+                    data.add(readEvdevEventCode(type));
+                    data.add(readInt());
                 }
                 mReader.endArray();
             } catch (IllegalStateException | NumberFormatException e) {
@@ -309,22 +345,32 @@
             return data;
         }
 
-        private byte[] readData() throws IOException {
-            ArrayList<Integer> data = readIntList();
-            byte[] rawData = new byte[data.size()];
-            for (int i = 0; i < data.size(); i++) {
-                int d = data.get(i);
-                if ((d & 0xFF) != d) {
-                    throw new IllegalStateException("Invalid data, all values must be byte-sized");
+        private int readValueAsInt(Function<String, Integer> stringToInt) throws IOException {
+            switch (mReader.peek()) {
+                case NUMBER: {
+                    return mReader.nextInt();
                 }
-                rawData[i] = (byte) d;
+                case STRING: {
+                    final var str = mReader.nextString();
+                    try {
+                        // Attempt to first parse the value as an int.
+                        return Integer.decode(str);
+                    } catch (NumberFormatException e) {
+                        // Then fall back to the supplied function.
+                        return stringToInt.apply(str);
+                    }
+                }
+                default: {
+                    throw new IllegalStateException(
+                            "Encountered malformed data. Expected int or string.");
+                }
             }
-            return rawData;
         }
 
         private int readInt() throws IOException {
-            String val = mReader.nextString();
-            return Integer.decode(val);
+            return readValueAsInt((str) -> {
+                throw new IllegalStateException("Encountered malformed data. Expected int.");
+            });
         }
 
         private Bus readBus() throws IOException {
@@ -338,17 +384,20 @@
             try {
                 mReader.beginArray();
                 while (mReader.hasNext()) {
-                    int type = 0;
+                    UinputControlCode controlCode = null;
                     IntStream data = null;
                     mReader.beginObject();
                     while (mReader.hasNext()) {
                         String name = mReader.nextName();
                         switch (name) {
                             case "type":
-                                type = readInt();
+                                controlCode = readUinputControlCode();
                                 break;
                             case "data":
-                                data = readIntList().stream().mapToInt(Integer::intValue);
+                                Objects.requireNonNull(controlCode,
+                                        "Configuration 'type' must be specified before 'data'.");
+                                data = readDataForControlCode(controlCode)
+                                        .stream().mapToInt(Integer::intValue);
                                 break;
                             default:
                                 consumeRemainingElements();
@@ -358,9 +407,9 @@
                         }
                     }
                     mReader.endObject();
-                    if (data != null) {
-                        final int[] existing = configuration.get(type);
-                        configuration.put(type, existing == null ? data.toArray()
+                    if (controlCode != null && data != null) {
+                        final int[] existing = configuration.get(controlCode.mValue);
+                        configuration.put(controlCode.mValue, existing == null ? data.toArray()
                                 : IntStream.concat(IntStream.of(existing), data).toArray());
                     }
                 }
@@ -373,6 +422,60 @@
             return configuration;
         }
 
+        private UinputControlCode readUinputControlCode() throws IOException {
+            var code = readValueAsInt((controlTypeStr) -> {
+                for (UinputControlCode controlCode : UinputControlCode.values()) {
+                    if (controlCode.mName.equals(controlTypeStr)) {
+                        return controlCode.mValue;
+                    }
+                }
+                return -1;
+            });
+            for (UinputControlCode controlCode : UinputControlCode.values()) {
+                if (controlCode.mValue == code) {
+                    return controlCode;
+                }
+            }
+            return null;
+        }
+
+        private List<Integer> readDataForControlCode(
+                UinputControlCode controlCode) throws IOException {
+            return switch (controlCode) {
+                case UI_SET_EVBIT -> readArrayAsInts(this::readEvdevEventType);
+                case UI_SET_KEYBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_KEY));
+                case UI_SET_RELBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_REL));
+                case UI_SET_ABSBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_ABS));
+                case UI_SET_MSCBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_MSC));
+                case UI_SET_LEDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_LED));
+                case UI_SET_SNDBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SND));
+                case UI_SET_FFBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_FF));
+                case UI_SET_SWBIT -> readArrayAsInts(() -> readEvdevEventCode(EV_SW));
+                case UI_SET_PROPBIT -> readArrayAsInts(this::readEvdevInputProp);
+            };
+        }
+
+        interface IntValueReader {
+            int readNextValue() throws IOException;
+        }
+
+        private ArrayList<Integer> readArrayAsInts(
+                IntValueReader nextValueReader) throws IOException {
+            ArrayList<Integer> data = new ArrayList<>();
+            try {
+                mReader.beginArray();
+                while (mReader.hasNext()) {
+                    data.add(nextValueReader.readNextValue());
+                }
+                mReader.endArray();
+            } catch (IllegalStateException | NumberFormatException e) {
+                consumeRemainingElements();
+                mReader.endArray();
+                throw new IllegalStateException("Encountered malformed data.", e);
+            }
+            return data;
+        }
+
         private InputAbsInfo readAbsInfo() throws IllegalStateException, IOException {
             InputAbsInfo absInfo = new InputAbsInfo();
             try {
@@ -426,7 +529,7 @@
                         String name = mReader.nextName();
                         switch (name) {
                             case "code":
-                                type = readInt();
+                                type = readEvdevEventCode(EV_ABS);
                                 break;
                             case "info":
                                 absInfo = readAbsInfo();
@@ -452,6 +555,18 @@
             return infoArray;
         }
 
+        private int readEvdevEventType() throws IOException {
+            return readValueAsInt(Device::getEvdevEventTypeByLabel);
+        }
+
+        private int readEvdevEventCode(int type) throws IOException {
+            return readValueAsInt((str) -> Device.getEvdevEventCodeByLabel(type, str));
+        }
+
+        private int readEvdevInputProp() throws IOException {
+            return readValueAsInt(Device::getEvdevInputPropByLabel);
+        }
+
         private void consumeRemainingElements() throws IOException {
             while (mReader.hasNext()) {
                 mReader.skipValue();
diff --git a/core/api/current.txt b/core/api/current.txt
index df8f581..f5db575 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26453,7 +26453,7 @@
     method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
     method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onClose();
-    method public void onDeviceStatusChanged(@Nullable android.media.midi.MidiDeviceStatus);
+    method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
     method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
     field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index c1f6219..cda9ae3 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,6 +6,7 @@
     field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
     field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
     field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
+    field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
   }
 
 }
@@ -80,6 +81,29 @@
 
 }
 
+package android.companion {
+
+  public final class CompanionDeviceManager {
+    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+    method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+    field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+    field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+    field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+  }
+
+  public static interface CompanionDeviceManager.OnMessageReceivedListener {
+    method public void onMessageReceived(int, @NonNull byte[]);
+  }
+
+  public static interface CompanionDeviceManager.OnTransportsChangedListener {
+    method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
+  }
+
+}
+
 package android.content {
 
   public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
@@ -95,6 +119,7 @@
     method @NonNull public android.os.IBinder getProcessToken();
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
+    field public static final String REMOTE_AUTH_SERVICE = "remote_auth";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 77168e3..0185080 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -647,6 +647,7 @@
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
+    field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio";
     field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
     field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
     field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 3fb9fd9..833d9c6 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -839,6 +839,7 @@
 
   public final class CompanionDeviceManager {
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
+    field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 58c2548..a09d7dc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -203,6 +203,7 @@
 import android.window.SizeConfigurationBuckets;
 import android.window.SplashScreen;
 import android.window.SplashScreenView;
+import android.window.WindowContextInfo;
 import android.window.WindowProviderService;
 import android.window.WindowTokenClientController;
 
@@ -6248,10 +6249,9 @@
     }
 
     @Override
-    public void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
-            @NonNull Configuration configuration, int displayId) {
-        WindowTokenClientController.getInstance().onWindowContextConfigurationChanged(clientToken,
-                configuration, displayId);
+    public void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
+            @NonNull WindowContextInfo info) {
+        WindowTokenClientController.getInstance().onWindowContextInfoChanged(clientToken, info);
     }
 
     @Override
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bcd43ea..096ad55 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2256,6 +2256,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
             "android:receive_sandbox_trigger_audio";
 
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f7a43f4..6753cb8 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -28,6 +28,7 @@
 import android.util.MergedConfiguration;
 import android.view.SurfaceControl;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
+import android.window.WindowContextInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.ReferrerIntent;
@@ -163,9 +164,9 @@
     public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
             Configuration overrideConfig, int displayId);
 
-    /** Deliver {@link android.window.WindowContext} configuration change. */
-    public abstract void handleWindowContextConfigurationChanged(@NonNull IBinder clientToken,
-            @NonNull Configuration configuration, int displayId);
+    /** Deliver {@link android.window.WindowContextInfo} change. */
+    public abstract void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
+            @NonNull WindowContextInfo info);
 
     /** Deliver {@link android.window.WindowContext} window removal event. */
     public abstract void handleWindowContextWindowRemoval(@NonNull IBinder clientToken);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 723c564..5d7993d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2929,19 +2929,16 @@
         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 (cmfWallpaperMap != null && cmfWallpaperMap.length > 0) {
+            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;
+                    }
                 }
             }
         }
@@ -2950,7 +2947,7 @@
             cn = null;
         }
 
-        return cn;
+        return cn == null ? getDefaultWallpaperComponent(context) : cn;
     }
 
     private static boolean isComponentExist(Context context, ComponentName cn) {
diff --git a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java b/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.java
deleted file mode 100644
index 3ac642f..0000000
--- a/core/java/android/app/servertransaction/WindowContextConfigurationChangeItem.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 android.app.servertransaction;
-
-import static android.view.Display.INVALID_DISPLAY;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ClientTransactionHandler;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.os.Parcel;
-
-import java.util.Objects;
-
-/**
- * {@link android.window.WindowContext} configuration change message.
- * @hide
- */
-public class WindowContextConfigurationChangeItem extends ClientTransactionItem {
-
-    @Nullable
-    private IBinder mClientToken;
-    @Nullable
-    private Configuration mConfiguration;
-    private int mDisplayId;
-
-    @Override
-    public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
-            @NonNull PendingTransactionActions pendingActions) {
-        client.handleWindowContextConfigurationChanged(mClientToken, mConfiguration, mDisplayId);
-    }
-
-    // ObjectPoolItem implementation
-
-    private WindowContextConfigurationChangeItem() {}
-
-    /** Obtains an instance initialized with provided params. */
-    public static WindowContextConfigurationChangeItem obtain(
-            @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
-        WindowContextConfigurationChangeItem instance =
-                ObjectPool.obtain(WindowContextConfigurationChangeItem.class);
-        if (instance == null) {
-            instance = new WindowContextConfigurationChangeItem();
-        }
-        instance.mClientToken = requireNonNull(clientToken);
-        instance.mConfiguration = requireNonNull(config);
-        instance.mDisplayId = displayId;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mClientToken = null;
-        mConfiguration = null;
-        mDisplayId = INVALID_DISPLAY;
-        ObjectPool.recycle(this);
-    }
-
-    // Parcelable implementation
-
-    /** Writes to Parcel. */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongBinder(mClientToken);
-        dest.writeTypedObject(mConfiguration, flags);
-        dest.writeInt(mDisplayId);
-    }
-
-    /** Reads from Parcel. */
-    private WindowContextConfigurationChangeItem(@NonNull Parcel in) {
-        mClientToken = in.readStrongBinder();
-        mConfiguration = in.readTypedObject(Configuration.CREATOR);
-        mDisplayId = in.readInt();
-    }
-
-    public static final @NonNull Creator<WindowContextConfigurationChangeItem> CREATOR =
-            new Creator<>() {
-                public WindowContextConfigurationChangeItem createFromParcel(Parcel in) {
-                    return new WindowContextConfigurationChangeItem(in);
-                }
-
-                public WindowContextConfigurationChangeItem[] newArray(int size) {
-                    return new WindowContextConfigurationChangeItem[size];
-                }
-    };
-
-    @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        final WindowContextConfigurationChangeItem other = (WindowContextConfigurationChangeItem) o;
-        return Objects.equals(mClientToken, other.mClientToken)
-                && Objects.equals(mConfiguration, other.mConfiguration)
-                && mDisplayId == other.mDisplayId;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + Objects.hashCode(mClientToken);
-        result = 31 * result + Objects.hashCode(mConfiguration);
-        result = 31 * result + mDisplayId;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "WindowContextConfigurationChangeItem{clientToken=" + mClientToken
-                + ", config=" + mConfiguration
-                + ", displayId=" + mDisplayId
-                + "}";
-    }
-}
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
new file mode 100644
index 0000000..74721d5
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.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 android.app.servertransaction;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ClientTransactionHandler;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.window.WindowContextInfo;
+
+import java.util.Objects;
+
+/**
+ * {@link android.window.WindowContext} configuration change message.
+ * @hide
+ */
+public class WindowContextInfoChangeItem extends ClientTransactionItem {
+
+    @Nullable
+    private IBinder mClientToken;
+    @Nullable
+    private WindowContextInfo mInfo;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        client.handleWindowContextInfoChanged(mClientToken, mInfo);
+    }
+
+    // ObjectPoolItem implementation
+
+    private WindowContextInfoChangeItem() {}
+
+    /** Obtains an instance initialized with provided params. */
+    public static WindowContextInfoChangeItem obtain(
+            @NonNull IBinder clientToken, @NonNull Configuration config, int displayId) {
+        WindowContextInfoChangeItem instance =
+                ObjectPool.obtain(WindowContextInfoChangeItem.class);
+        if (instance == null) {
+            instance = new WindowContextInfoChangeItem();
+        }
+        instance.mClientToken = requireNonNull(clientToken);
+        instance.mInfo = new WindowContextInfo(config, displayId);
+
+        return instance;
+    }
+
+    @Override
+    public void recycle() {
+        mClientToken = null;
+        mInfo = null;
+        ObjectPool.recycle(this);
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mClientToken);
+        dest.writeTypedObject(mInfo, flags);
+    }
+
+    /** Reads from Parcel. */
+    private WindowContextInfoChangeItem(@NonNull Parcel in) {
+        mClientToken = in.readStrongBinder();
+        mInfo = in.readTypedObject(WindowContextInfo.CREATOR);
+    }
+
+    public static final @NonNull Creator<WindowContextInfoChangeItem> CREATOR =
+            new Creator<>() {
+                public WindowContextInfoChangeItem createFromParcel(Parcel in) {
+                    return new WindowContextInfoChangeItem(in);
+                }
+
+                public WindowContextInfoChangeItem[] newArray(int size) {
+                    return new WindowContextInfoChangeItem[size];
+                }
+    };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowContextInfoChangeItem other = (WindowContextInfoChangeItem) o;
+        return Objects.equals(mClientToken, other.mClientToken)
+                && Objects.equals(mInfo, other.mInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mClientToken);
+        result = 31 * result + Objects.hashCode(mInfo);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowContextInfoChangeItem{clientToken=" + mClientToken
+                + ", info=" + mInfo
+                + "}";
+    }
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index e45fa19..69e1653 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -20,7 +20,9 @@
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_COMPUTER;
 import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -209,6 +211,34 @@
     public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
 
     /**
+     * Test message type without a designated callback.
+     *
+     * @hide
+     */
+    @TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+    /**
+     * Message header assigned to the remote authentication handshakes.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
+    /**
+     * Message header assigned to the telecom context sync metadata.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
+    /**
+     * Message header assigned to the permission restore request.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+    /**
      * Callback for applications to receive updates about and the outcome of
      * {@link AssociationRequest} issued via {@code associate()} call.
      *
@@ -797,28 +827,36 @@
     }
 
     /**
-     * Listener for any changes to {@link com.android.server.companion.transport.Transport}.
+     * Listener for any changes to the list of attached transports.
+     *
+     * @see com.android.server.companion.transport.Transport
      *
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public interface OnTransportsChangedListener {
         /**
-         * Invoked when a change occurs to any of the transports
+         * Invoked when a transport is attached or detached.
          *
-         * @param associations all the associations which have connected transports
+         * @param associations all the associations which have connected transports.
          */
         void onTransportsChanged(@NonNull List<AssociationInfo> associations);
     }
 
     /**
-     * Register a listener for any changes to
-     * {@link com.android.server.companion.transport.Transport}. Your app will receive a callback to
-     * {@link OnTransportsChangedListener} immediately with all the existing transports.
+     * Adds a listener for any changes to the list of attached transports.
+     * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
+     * of existing transports when a transport is detached or a new transport is attached.
+     *
+     * @see com.android.server.companion.transport.Transport
      *
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void addOnTransportsChangedListener(
-            @NonNull Executor executor, @NonNull OnTransportsChangedListener listener) {
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnTransportsChangedListener listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
                 executor, listener);
         try {
@@ -829,11 +867,14 @@
     }
 
     /**
-     * Unregister a listener to stop receiving any changes to
-     * {@link com.android.server.companion.transport.Transport}.
+     * Removes the registered listener for any changes to the list of attached transports.
+     *
+     * @see com.android.server.companion.transport.Transport
      *
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnTransportsChangedListener(
             @NonNull OnTransportsChangedListener listener) {
         final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
@@ -846,11 +887,16 @@
     }
 
     /**
-     * Send a message to remote devices
+     * Sends a message to associated remote devices. The target associations must already have a
+     * connected transport.
+     *
+     * @see #attachSystemDataTransport(int, InputStream, OutputStream)
      *
      * @hide
      */
-    public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
+    public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
         try {
             mService.sendMessage(messageType, data, associationIds);
         } catch (RemoteException e) {
@@ -859,28 +905,30 @@
     }
 
     /**
-     * Listener when a message is received for the registered message type
+     * Listener that triggers a callback when a message is received through a connected transport.
      *
      * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
      *
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public interface OnMessageReceivedListener {
         /**
-         * Called when a message is received
+         * Called when a message is received.
          */
-        void onMessageReceived(int associationId, byte[] data);
+        void onMessageReceived(int associationId, @NonNull byte[] data);
     }
 
     /**
-     * Register a listener to receive callbacks when a message is received by the given type
-     *
-     * @see com.android.server.companion.transport.Transport for supported message types
+     * Adds a listener to trigger callbacks when messages of given type are received.
      *
      * @hide
      */
-    public void addOnMessageReceivedListener(@NonNull Executor executor, int messageType,
-            OnMessageReceivedListener listener) {
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
+    public void addOnMessageReceivedListener(
+            @NonNull @CallbackExecutor Executor executor, int messageType,
+            @NonNull OnMessageReceivedListener listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 executor, listener);
         try {
@@ -891,15 +939,14 @@
     }
 
     /**
-     * Unregister a listener to stop receiving callbacks when a message is received by the given
-     * type
-     *
-     * @see com.android.server.companion.transport.Transport for supported message types
+     * Removes the registered listener for received messages of given type.
      *
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
     public void removeOnMessageReceivedListener(int messageType,
-            OnMessageReceivedListener listener) {
+            @NonNull OnMessageReceivedListener listener) {
         final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
                 null, listener);
         try {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 2318e30..463dba2 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -74,14 +74,19 @@
     @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
 
+    @EnforcePermission("USE_COMPANION_TRANSPORTS")
     void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
 
+    @EnforcePermission("USE_COMPANION_TRANSPORTS")
     void removeOnTransportsChangedListener(IOnTransportsChangedListener listener);
 
+    @EnforcePermission("USE_COMPANION_TRANSPORTS")
     void sendMessage(int messageType, in byte[] data, in int[] associationIds);
 
+    @EnforcePermission("USE_COMPANION_TRANSPORTS")
     void addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
 
+    @EnforcePermission("USE_COMPANION_TRANSPORTS")
     void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
 
     void notifyDeviceAppeared(int associationId);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b2cd7e9..9253998 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1985,7 +1985,7 @@
      * Open a raw file descriptor to access (potentially type transformed)
      * data from a "content:" URI.  This interacts with the underlying
      * {@link ContentProvider#openTypedAssetFile} method of the provider
-     * associated with the given URI, to retrieve retrieve any appropriate
+     * associated with the given URI, to retrieve any appropriate
      * data stream for the data stored there.
      *
      * <p>Unlike {@link #openAssetFileDescriptor}, this function only works
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2a6d84b..c11a8fc 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6341,6 +6341,18 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.remoteauth.RemoteAuthManager} to discover,
+     * register and authenticate via remote authenticator  devices.
+     *
+     * @see #getSystemService(String)
+     * @see android.remoteauth.RemoteAuthManager
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String REMOTE_AUTH_SERVICE = "remote_auth";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.app.ambientcontext.AmbientContextManager}.
      *
      * @see #getSystemService(String)
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 3e0ab90..a86c0c9 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.util.Pools.SimplePool;
+import android.util.Slog;
 
 import androidx.annotation.StyleableRes;
 
@@ -41,6 +42,7 @@
     public static final int MAX_ATTR_LEN_PATH = 4000;
     public static final int MAX_ATTR_LEN_DATA_VALUE = 4000;
 
+    private static final String TAG = "PackageParsing";
     protected static final String TAG_ACTION = "action";
     protected static final String TAG_ACTIVITY = "activity";
     protected static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";
@@ -715,23 +717,123 @@
         mChildTagMask |= 1 << idx;
     }
 
+    private boolean isComponentNameAttr(String name) {
+        switch (mTag) {
+            case TAG_ACTIVITY:
+                switch (name) {
+                    case TAG_ATTR_NAME:
+                    case TAG_ATTR_PARENT_ACTIVITY_NAME:
+                        return true;
+                    default:
+                        return false;
+                }
+            case TAG_ACTIVITY_ALIAS:
+                switch (name) {
+                    case TAG_ATTR_TARGET_ACTIVITY:
+                        return true;
+                    default:
+                        return false;
+                }
+            case TAG_APPLICATION:
+                switch (name) {
+                    case TAG_ATTR_BACKUP_AGENT:
+                    case TAG_ATTR_NAME:
+                        return true;
+                    default:
+                        return false;
+                }
+            case TAG_INSTRUMENTATION:
+            case TAG_PROVIDER:
+            case TAG_RECEIVER:
+            case TAG_SERVICE:
+                switch (name) {
+                    case TAG_ATTR_NAME:
+                        return true;
+                    default:
+                        return false;
+                }
+            default:
+                return false;
+        }
+    }
+
+    private boolean isComponentNameAttr(@StyleableRes int index) {
+        switch (mTag) {
+            case TAG_ACTIVITY:
+                return index == R.styleable.AndroidManifestActivity_name
+                        || index == R.styleable.AndroidManifestActivity_parentActivityName;
+            case TAG_ACTIVITY_ALIAS:
+                return index == R.styleable.AndroidManifestActivityAlias_targetActivity;
+            case TAG_APPLICATION:
+                return index == R.styleable.AndroidManifestApplication_backupAgent
+                        || index == R.styleable.AndroidManifestApplication_name;
+            case TAG_INSTRUMENTATION:
+                return index ==  R.styleable.AndroidManifestInstrumentation_name;
+            case TAG_PROVIDER:
+                return index ==  R.styleable.AndroidManifestProvider_name;
+            case TAG_RECEIVER:
+                return index ==  R.styleable.AndroidManifestReceiver_name;
+            case TAG_SERVICE:
+                return index ==  R.styleable.AndroidManifestService_name;
+            default:
+                return false;
+        }
+    }
+
     boolean hasChild(String tag) {
         return (mChildTagMask & (1 << getCounterIdx(tag))) != 0;
     }
 
+    void validateComponentName(CharSequence name) {
+        int i = 0;
+        if (name.charAt(0) == '.') {
+            i = 1;
+        }
+        boolean isStart = true;
+        for (; i < name.length(); i++) {
+            if (name.charAt(i) == '.') {
+                if (isStart) {
+                    break;
+                }
+                isStart = true;
+            } else {
+                if (isStart) {
+                    if (Character.isJavaIdentifierStart(name.charAt(i))) {
+                        isStart = false;
+                    } else {
+                        break;
+                    }
+                } else if (!Character.isJavaIdentifierPart(name.charAt(i))) {
+                    break;
+                }
+            }
+        }
+        if ((i < name.length()) || (name.charAt(name.length() - 1) == '.')) {
+            Slog.e(TAG, name + " is not a valid Java class name");
+            throw new SecurityException(name + " is not a valid Java class name");
+        }
+    }
+
     void validateStrAttr(String attrName, String attrValue) {
         if (attrValue != null && attrValue.length() > getAttrStrMaxLen(attrName)) {
             throw new SecurityException("String length limit exceeded for attribute " + attrName
                     + " in " + mTag);
         }
+        if (isComponentNameAttr(attrName)) {
+            validateComponentName(attrValue);
+        }
     }
 
     void validateResStrAttr(@StyleableRes int index, CharSequence stringValue) {
         if (stringValue != null && stringValue.length() > getResStrMaxLen(index)) {
             throw new SecurityException("String length limit exceeded for attribute in " + mTag);
         }
+        if (isComponentNameAttr(index)) {
+            validateComponentName(stringValue);
+        }
     }
 
+
     void seen(@NonNull Element element) {
         TagCounter counter = mTagCounters[getCounterIdx(element.mTag)];
         if (counter != null) {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3a66081..c872516 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3535,7 +3535,7 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * They can be queried through
      * {@link android.hardware.camera2.CameraCharacteristics#get } with
-     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
      * Unless reported by both
      * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -3550,13 +3550,12 @@
      * <ul>
      * <li>
      * <p>The mandatory stream combinations listed in
-     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
-     *   would not apply.</p>
+     *   {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations}  would not apply.</p>
      * </li>
      * <li>
      * <p>The bayer pattern of {@code RAW} streams when
      *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     *   is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
      * </li>
      * <li>
      * <p>The following keys will always be present:</p>
@@ -3576,9 +3575,11 @@
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
+     * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1536376..57f7bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4460,7 +4460,7 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * They can be queried through
      * {@link android.hardware.camera2.CameraCharacteristics#get } with
-     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
      * Unless reported by both
      * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -4475,13 +4475,12 @@
      * <ul>
      * <li>
      * <p>The mandatory stream combinations listed in
-     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
-     *   would not apply.</p>
+     *   {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations}  would not apply.</p>
      * </li>
      * <li>
      * <p>The bayer pattern of {@code RAW} streams when
      *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     *   is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
      * </li>
      * <li>
      * <p>The following keys will always be present:</p>
@@ -4501,9 +4500,11 @@
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
+     * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index ae700a0..e06699b 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -266,7 +266,7 @@
         List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
         for (CameraOutputConfig output : outputConfigs) {
-            Surface outputSurface = initializeSurfrace(output);
+            Surface outputSurface = initializeSurface(output);
             if (outputSurface == null) {
                 continue;
             }
@@ -279,7 +279,7 @@
             if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) {
                 cameraOutput.enableSurfaceSharing();
                 for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) {
-                    Surface sharedSurface = initializeSurfrace(sharedOutputConfig);
+                    Surface sharedSurface = initializeSurface(sharedOutputConfig);
                     if (sharedSurface == null) {
                         continue;
                     }
@@ -1159,7 +1159,7 @@
         return ret;
     }
 
-    private Surface initializeSurfrace(CameraOutputConfig output) {
+    private Surface initializeSurface(CameraOutputConfig output) {
         switch(output.type) {
             case CameraOutputConfig.TYPE_SURFACE:
                 if (output.surface == null) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 1db4808..5d25681 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -2110,10 +2110,10 @@
             HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap, Integer jpegOrientation,
             Byte jpegQuality) {
         ArrayList<CaptureBundle> ret = new ArrayList<>();
-        for (Integer stagetId : captureMap.keySet()) {
-            Pair<Image, TotalCaptureResult> entry = captureMap.get(stagetId);
+        for (Integer stageId : captureMap.keySet()) {
+            Pair<Image, TotalCaptureResult> entry = captureMap.get(stageId);
             CaptureBundle bundle = new CaptureBundle();
-            bundle.stage = stagetId;
+            bundle.stage = stageId;
             bundle.captureImage = initializeParcelImage(entry.first);
             bundle.sequenceId = entry.second.getSequenceId();
             bundle.captureResult = entry.second.getNativeMetadata();
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7b5dd55..795eb4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1019,8 +1019,9 @@
             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());
@@ -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
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index db1a741..f32a1f8 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -24,7 +24,9 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * A CombinedVibration describes a combination of haptic effects to be performed by one or more
@@ -151,6 +153,13 @@
     public abstract boolean hasVibrator(int vibratorId);
 
     /**
+     * Returns a compact version of the {@link #toString()} result for debugging purposes.
+     *
+     * @hide
+     */
+    public abstract String toDebugString();
+
+    /**
      * Adapts a {@link VibrationEffect} to a specific device vibrator using the ID.
      *
      * <p>This can be used for adapting effects to the capabilities of the specific device vibrator
@@ -437,6 +446,13 @@
             return "Mono{mEffect=" + mEffect + '}';
         }
 
+        /** @hide */
+        @Override
+        public String toDebugString() {
+            // Simplify vibration string, use the single effect to represent it.
+            return mEffect.toDebugString();
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -619,6 +635,17 @@
             return "Stereo{mEffects=" + mEffects + '}';
         }
 
+        /** @hide */
+        @Override
+        public String toDebugString() {
+            StringJoiner sj = new StringJoiner(",", "Stereo{", "}");
+            for (int i = 0; i < mEffects.size(); i++) {
+                sj.add(String.format(Locale.ROOT, "vibrator(id=%d): %s",
+                        mEffects.keyAt(i), mEffects.valueAt(i).toDebugString()));
+            }
+            return sj.toString();
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -833,6 +860,17 @@
             return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
         }
 
+        /** @hide */
+        @Override
+        public String toDebugString() {
+            StringJoiner sj = new StringJoiner(",", "Sequential{", "}");
+            for (int i = 0; i < mEffects.size(); i++) {
+                sj.add(String.format(Locale.ROOT, "delayMs=%d, effect=%s",
+                        mDelays.get(i), mEffects.get(i).toDebugString()));
+            }
+            return sj.toString();
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 299e7f1..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,7 +115,6 @@
     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";
 
@@ -196,16 +195,15 @@
     }
 
     /**
-     * Query to determine the ANGLE driver choice.
+     * Query to determine if ANGLE should be used
      */
-    private String queryAngleChoice(Context context, Bundle coreSettings,
-                                               String packageName) {
+    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return false;
         }
 
-        return queryAngleChoiceInternal(context, coreSettings, packageName);
+        return shouldUseAngleInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -426,11 +424,10 @@
      *    forces a choice;
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private String queryAngleChoiceInternal(Context context, Bundle bundle,
-                                                       String packageName) {
+    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return false;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -445,7 +442,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+            return true;
         }
 
         // Get the per-application settings lists
@@ -468,8 +465,7 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
 
         // See if this application is listed in the per-application settings list
@@ -477,8 +473,7 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -489,14 +484,13 @@
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
         if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+            return true;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return ANGLE_GL_DRIVER_CHOICE_NATIVE;
+            return false;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
             // the game mode indicates
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
     }
 
@@ -563,12 +557,8 @@
      */
     private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String 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);
+
+        if (!shouldUseAngle(context, bundle, packageName)) {
             return false;
         }
 
@@ -637,10 +627,10 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
-        // application package name and ANGLE features to use.
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        nativeSetAngleInfo(paths, false, packageName, features);
+        setAngleInfo(paths, false, packageName, features);
 
         return true;
     }
@@ -662,10 +652,10 @@
             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.
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
+        setAngleInfo("", true, packageName, features);
         return true;
     }
 
@@ -946,8 +936,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 nativeSetAngleInfo(String path, boolean useNativeDriver,
-            String packageName, String[] features);
+    private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+            String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index f79d6e6..977ef60 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -29,6 +29,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.Reference;
+import java.util.Objects;
 
 
 /** The PerformanceHintManager allows apps to send performance hint to system. */
@@ -239,6 +240,7 @@
             if (mNativeSessionPtr == 0) {
                 return;
             }
+            Objects.requireNonNull(tids, "tids cannot be null");
             if (tids.length == 0) {
                 throw new IllegalArgumentException("Thread id list can't be empty.");
             }
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 954ee3c..dae9b5e 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -27,6 +27,19 @@
       ]
     },
     {
+      "file_patterns": [
+        "[^/]*(Vibrator|Vibration)[^/]*\\.java",
+        "vibrator/.*"
+      ],
+      "name": "CtsVibratorTestCases",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
       "file_patterns": ["Bugreport[^/]*\\.java"],
       "name": "BugreportManagerTestCases",
       "options": [
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index b2d62eb..98f9dff 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -336,10 +336,11 @@
 
     @Override
     public String toString() {
-        return "VibrationAttributes:"
-                + " Usage=" + usageToString()
-                + " Audio Usage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
-                + " Flags=" + mFlags;
+        return "VibrationAttributes{"
+                + "mUsage=" + usageToString()
+                + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+                + ", mFlags=" + mFlags
+                + '}';
     }
 
     /** @hide */
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0461b2e..b24b45d 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -44,7 +44,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
+import java.util.StringJoiner;
 
 /**
  * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -232,11 +234,11 @@
      * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
      * vibration components) that is equivalent to this VibrationEffect.
      *
-     * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
-     * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
-     * created with other means becomes converted into an equivalent legacy vibration pattern, even
-     * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
-     * vibration pattern for such effects, it will return {@code null}.
+     * <p>All non-repeating effects created with {@link #createWaveform(long[], int)} are
+     * convertible into an equivalent vibration pattern with this method. It is not guaranteed that
+     * an effect created with other means becomes converted into an equivalent legacy vibration
+     * pattern, even if it has an equivalent vibration pattern. If this method is unable to create
+     * an equivalent vibration pattern for such effects, it will return {@code null}.
      *
      * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
      * form of repeating behavior, regardless of how the effect was created. For repeating effects,
@@ -245,7 +247,7 @@
      * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
      *               the method successfully derived a vibration pattern equivalent to the effect
      *               (this will always be the case if the effect was created via
-     *               {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+     *               {@link #createWaveform(long[], int)} and is non-repeating). Otherwise, returns
      *               {@code null}.
      * @hide
      */
@@ -629,6 +631,13 @@
         return MathUtils.constrain(a * fx, 0f, 1f);
     }
 
+    /**
+     * Returns a compact version of the {@link #toString()} result for debugging purposes.
+     *
+     * @hide
+     */
+    public abstract String toDebugString();
+
     /** @hide */
     public static String effectIdToString(int effectId) {
         switch (effectId) {
@@ -925,6 +934,23 @@
                     + "}";
         }
 
+        /** @hide */
+        @Override
+        public String toDebugString() {
+            if (mSegments.size() == 1 && mRepeatIndex < 0) {
+                // Simplify effect string, use the single segment to represent it.
+                return mSegments.get(0).toDebugString();
+            }
+            StringJoiner sj = new StringJoiner(",", "[", "]");
+            for (int i = 0; i < mSegments.size(); i++) {
+                sj.add(mSegments.get(i).toDebugString());
+            }
+            if (mRepeatIndex >= 0) {
+                return String.format(Locale.ROOT, "%s, repeat=%d", sj, mRepeatIndex);
+            }
+            return sj.toString();
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -953,7 +979,7 @@
         /**
          * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
          * only if it can possibly be a segment for an effect created via
-         * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+         * {@link #createWaveform(long[], int)}. Otherwise, returns {@code null}.
          */
         @Nullable
         private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
@@ -1250,23 +1276,23 @@
         public static String primitiveToString(@PrimitiveType int id) {
             switch (id) {
                 case PRIMITIVE_NOOP:
-                    return "PRIMITIVE_NOOP";
+                    return "NOOP";
                 case PRIMITIVE_CLICK:
-                    return "PRIMITIVE_CLICK";
+                    return "CLICK";
                 case PRIMITIVE_THUD:
-                    return "PRIMITIVE_THUD";
+                    return "THUD";
                 case PRIMITIVE_SPIN:
-                    return "PRIMITIVE_SPIN";
+                    return "SPIN";
                 case PRIMITIVE_QUICK_RISE:
-                    return "PRIMITIVE_QUICK_RISE";
+                    return "QUICK_RISE";
                 case PRIMITIVE_SLOW_RISE:
-                    return "PRIMITIVE_SLOW_RISE";
+                    return "SLOW_RISE";
                 case PRIMITIVE_QUICK_FALL:
-                    return "PRIMITIVE_QUICK_FALL";
+                    return "QUICK_FALL";
                 case PRIMITIVE_TICK:
-                    return "PRIMITIVE_TICK";
+                    return "TICK";
                 case PRIMITIVE_LOW_TICK:
-                    return "PRIMITIVE_LOW_TICK";
+                    return "LOW_TICK";
                 default:
                     return Integer.toString(id);
             }
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 71ec096..02e6856 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.vibrator.Braking;
 import android.hardware.vibrator.IVibrator;
+import android.util.IndentingPrintWriter;
 import android.util.MathUtils;
 import android.util.Range;
 import android.util.SparseBooleanArray;
@@ -207,6 +208,25 @@
                 + '}';
     }
 
+    /** @hide */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("VibratorInfo:");
+        pw.increaseIndent();
+        pw.println("id = " + mId);
+        pw.println("capabilities = " + Arrays.toString(getCapabilitiesNames()));
+        pw.println("capabilitiesFlags = " + Long.toBinaryString(mCapabilities));
+        pw.println("supportedEffects = " + Arrays.toString(getSupportedEffectsNames()));
+        pw.println("supportedPrimitives = " + Arrays.toString(getSupportedPrimitivesNames()));
+        pw.println("supportedBraking = " + Arrays.toString(getSupportedBrakingNames()));
+        pw.println("primitiveDelayMax = " + mPrimitiveDelayMax);
+        pw.println("compositionSizeMax = " + mCompositionSizeMax);
+        pw.println("pwlePrimitiveDurationMax = " + mPwlePrimitiveDurationMax);
+        pw.println("pwleSizeMax = " + mPwleSizeMax);
+        pw.println("q-factor = " + mQFactor);
+        pw.println("frequencyProfile = " + mFrequencyProfile);
+        pw.decreaseIndent();
+    }
+
     /** Return the id of this vibrator. */
     public int getId() {
         return mId;
@@ -370,7 +390,7 @@
      * Gets the resonant frequency of the vibrator.
      *
      * @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     *         this vibrator is a composite of multiple physical devices.
+     * this vibrator is a composite of multiple physical devices.
      */
     public float getResonantFrequencyHz() {
         return mFrequencyProfile.mResonantFrequencyHz;
@@ -380,7 +400,7 @@
      * Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
      *
      * @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
-     *         this vibrator is a composite of multiple physical devices.
+     * this vibrator is a composite of multiple physical devices.
      */
     public float getQFactor() {
         return mQFactor;
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index da2ee6c..42b6c2da 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -119,12 +119,6 @@
     }
 
     /** @hide */
-    @Override
-    public boolean hasNonZeroAmplitude() {
-        return true;
-    }
-
-    /** @hide */
     @NonNull
     @Override
     public PrebakedSegment resolve(int defaultAmplitude) {
@@ -209,6 +203,15 @@
                 + "}";
     }
 
+    /** @hide */
+    @Override
+    public String toDebugString() {
+        return String.format("Prebaked=%s(%s, %s fallback)",
+                VibrationEffect.effectIdToString(mEffectId),
+                VibrationEffect.effectStrengthToString(mEffectStrength),
+                mFallback ? "with" : "no");
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index e1fa97b..c52a09c 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -88,13 +88,6 @@
     }
 
     /** @hide */
-    @Override
-    public boolean hasNonZeroAmplitude() {
-        // Every primitive plays a vibration with a non-zero amplitude, even at scale == 0.
-        return true;
-    }
-
-    /** @hide */
     @NonNull
     @Override
     public PrimitiveSegment resolve(int defaultAmplitude) {
@@ -147,6 +140,13 @@
                 + '}';
     }
 
+    /** @hide */
+    @Override
+    public String toDebugString() {
+        return String.format("Primitive=%s(scale=%.2f, delay=%dms)",
+                VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, mDelay);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) return true;
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 034962a..e997bcd 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -125,12 +125,6 @@
 
     /** @hide */
     @Override
-    public boolean hasNonZeroAmplitude() {
-        return mStartAmplitude > 0 || mEndAmplitude > 0;
-    }
-
-    /** @hide */
-    @Override
     public void validate() {
         VibrationEffectSegment.checkFrequencyArgument(mStartFrequencyHz, "startFrequencyHz");
         VibrationEffectSegment.checkFrequencyArgument(mEndFrequencyHz, "endFrequencyHz");
@@ -185,6 +179,17 @@
                 + "}";
     }
 
+    /** @hide */
+    @Override
+    public String toDebugString() {
+        return String.format("Ramp=%dms(amplitude=%.2f%s to %.2f%s)",
+                mDuration,
+                mStartAmplitude,
+                Float.compare(mStartFrequencyHz, 0) == 0 ? "" : " @ " + mStartFrequencyHz + "Hz",
+                mEndAmplitude,
+                Float.compare(mEndFrequencyHz, 0) == 0 ? "" : " @ " + mEndFrequencyHz + "Hz");
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 817187e..a585aa8 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -101,13 +101,6 @@
 
     /** @hide */
     @Override
-    public boolean hasNonZeroAmplitude() {
-        // DEFAULT_AMPLITUDE == -1 is still a non-zero amplitude that will be resolved later.
-        return Float.compare(mAmplitude, 0) != 0;
-    }
-
-    /** @hide */
-    @Override
     public void validate() {
         VibrationEffectSegment.checkFrequencyArgument(mFrequencyHz, "frequencyHz");
         VibrationEffectSegment.checkDurationArgument(mDuration, "duration");
@@ -168,6 +161,13 @@
                 + "}";
     }
 
+    /** @hide */
+    @Override
+    public String toDebugString() {
+        return String.format("Step=%dms(amplitude=%.2f%s)", mDuration, mAmplitude,
+                Float.compare(mFrequencyHz, 0) == 0 ? "" : " @ " + mFrequencyHz + "Hz");
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 4790d81..bde334a 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -32,6 +32,9 @@
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.os.Vibrator.VibrationIntensity;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
 
 /**
  * List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -191,4 +194,18 @@
                 + ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
                 + "}";
     }
+
+    /**
+     * Write current settings into given {@link PrintWriter}, skipping the default settings.
+     *
+     * @hide
+     */
+    public void dumpWithoutDefaultSettings(IndentingPrintWriter pw) {
+        pw.println("VibrationConfig:");
+        pw.increaseIndent();
+        pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
+        pw.println("rampStepDurationMs = " + mRampStepDurationMs);
+        pw.println("rampDownDurationMs = " + mRampDownDurationMs);
+        pw.decreaseIndent();
+    }
 }
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 75a055f..3b286a7 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -76,13 +76,6 @@
     public abstract boolean isHapticFeedbackCandidate();
 
     /**
-     * Returns true if this segment plays at a non-zero amplitude at some point.
-     *
-     * @hide
-     */
-    public abstract boolean hasNonZeroAmplitude();
-
-    /**
      * Validates the segment, throwing exceptions if any parameter is invalid.
      *
      * @hide
@@ -123,6 +116,13 @@
     public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
 
     /**
+     * Returns a compact version of the {@link #toString()} result for debugging purposes.
+     *
+     * @hide
+     */
+    public abstract String toDebugString();
+
+    /**
      * Checks the given frequency argument is valid to represent a vibration effect frequency in
      * hertz, i.e. a finite non-negative value.
      *
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 3c5757d..5d6dfc7 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -63,6 +63,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -1956,9 +1957,8 @@
                     userManager.isUserUnlocked(user) ? CONTENT_URI : SHADOW_CONTENT_URI,
                     user.getIdentifier());
 
-            if (VERBOSE_LOG) {
-                Log.v(LOG_TAG, String.format("Inserting to %s", uri));
-            }
+            Log.i(LOG_TAG, String.format(Locale.getDefault(),
+                    "addEntryAndRemoveExpiredEntries: provider uri=%s", uri));
 
             try {
                 // When cleaning up the call log, try to delete older call long entries on a per
diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING
index b26a38b..a3a86fd 100644
--- a/core/java/android/util/apk/TEST_MAPPING
+++ b/core/java/android/util/apk/TEST_MAPPING
@@ -15,7 +15,7 @@
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
         },
         {
-          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallerTest"
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallTest"
         }
       ]
     }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c1474eb..d3b7a5b 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -71,6 +71,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ScreenCapture;
+import android.window.WindowContextInfo;
 
 /**
  * System private interface to the window manager.
@@ -858,10 +859,10 @@
      * @param displayId The display associated with the window context
      * @param options A bundle used to pass window-related options and choose the right DisplayArea
      *
-     * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
-     * attached to the DisplayArea successfully. {@code null}, otherwise.
+     * @return the {@link WindowContextInfo} of the DisplayArea if the WindowContext is attached to
+     * the DisplayArea successfully. {@code null}, otherwise.
      */
-    @nullable Configuration attachWindowContextToDisplayArea(in IApplicationThread appThread,
+    @nullable WindowContextInfo attachWindowContextToDisplayArea(in IApplicationThread appThread,
             IBinder clientToken, int type, int displayId, in @nullable Bundle options);
 
     /**
@@ -879,13 +880,15 @@
      * the WindowContext's token}
      * @param token the WindowToken to attach
      *
+     * @return the {@link WindowContextInfo} of the WindowToken if the WindowContext is attached to
+     * the WindowToken successfully. {@code null}, otherwise.
      * @throws IllegalArgumentException if the {@code clientToken} have not been attached to
      * the server or the WindowContext's type doesn't match WindowToken {@code token}'s type.
      *
      * @see #attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
      */
-    void attachWindowContextToWindowToken(in IApplicationThread appThread, IBinder clientToken,
-            IBinder token);
+    @nullable WindowContextInfo  attachWindowContextToWindowToken(in IApplicationThread appThread,
+            IBinder clientToken, IBinder token);
 
     /**
      * Attaches a {@code clientToken} to associate with DisplayContent.
@@ -899,11 +902,11 @@
      * the WindowContext's token}
      * @param displayId The display associated with the window context
      *
-     * @return the DisplayContent's {@link android.app.res.Configuration} if the Context is
-     * attached to the DisplayContent successfully. {@code null}, otherwise.
+     * @return the {@link WindowContextInfo} of the DisplayContent if the WindowContext is attached
+     * to the DisplayContent successfully. {@code null}, otherwise.
      * @throws android.view.WindowManager.InvalidDisplayException if the display ID is invalid
      */
-    @nullable Configuration attachWindowContextToDisplayContent(in IApplicationThread appThread,
+    @nullable WindowContextInfo attachWindowContextToDisplayContent(in IApplicationThread appThread,
             IBinder clientToken, int displayId);
 
     /**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 17afd55..4ecfc40 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -844,12 +844,12 @@
     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;
@@ -862,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())) {
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c13b9ab..af24140 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -380,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) {
@@ -696,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; }
 
@@ -721,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;
@@ -756,7 +767,7 @@
                 thisIndex++;
                 thatIndex++;
             }
-            return thisIndex >= thisSize && thatIndex >= thatSize;
+            return true;
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 01a99b9..1b1098d 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2539,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/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 6aa8506..d20d95d 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -34,7 +34,6 @@
 import android.util.Log;
 import android.view.inputmethod.InputMethodManager;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.FileDescriptor;
@@ -165,6 +164,7 @@
         }
     }
 
+    @Nullable
     @UnsupportedAppUsage
     public static IWindowManager getWindowManagerService() {
         synchronized (WindowManagerGlobal.class) {
@@ -172,6 +172,7 @@
                 sWindowManagerService = IWindowManager.Stub.asInterface(
                         ServiceManager.getService("window"));
                 try {
+                    // Can be null if this is called before WindowManagerService is initialized.
                     if (sWindowManagerService != null) {
                         ValueAnimator.setDurationScale(
                                 sWindowManagerService.getCurrentAnimatorScale());
@@ -185,15 +186,6 @@
         }
     }
 
-    /** Overrides the {@link #getWindowManagerService()} for test only. */
-    @VisibleForTesting
-    public static void overrideWindowManagerServiceForTesting(
-            @NonNull IWindowManager windowManager) {
-        synchronized (WindowManagerGlobal.class) {
-            sWindowManagerService = windowManager;
-        }
-    }
-
     @UnsupportedAppUsage
     public static IWindowSession getWindowSession() {
         synchronized (WindowManagerGlobal.class) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6ad1960..03364b6 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -916,6 +916,8 @@
         }
     }
 
+    private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+
     public AbsListView(Context context) {
         super(context);
         setupDeviceConfigProperties();
@@ -4488,17 +4490,22 @@
     public boolean onGenericMotionEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_SCROLL:
-                final float axisValue;
+                final int axis;
                 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
-                    axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+                    axis = MotionEvent.AXIS_VSCROLL;
                 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
-                    axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
+                    axis = MotionEvent.AXIS_SCROLL;
                 } else {
-                    axisValue = 0;
+                    axis = -1;
                 }
 
+                final float axisValue = (axis == -1) ? 0 : event.getAxisValue(axis);
                 final int delta = Math.round(axisValue * mVerticalScrollFactor);
                 if (delta != 0) {
+                    // Tracks whether or not we should attempt fling for this event.
+                    // Fling should not be attempted if the view is already at the limit of scroll,
+                    // since it conflicts with EdgeEffect.
+                    boolean shouldAttemptFling = true;
                     // If we're moving down, we want the top item. If we're moving up, bottom item.
                     final int motionIndex = delta > 0 ? 0 : getChildCount() - 1;
 
@@ -4511,6 +4518,10 @@
                     final int overscrollMode = getOverScrollMode();
 
                     if (!trackMotionScroll(delta, delta)) {
+                        if (shouldAttemptFling) {
+                            initDifferentialFlingHelperIfNotExists();
+                            mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
+                        }
                         return true;
                     } else if (!event.isFromSource(InputDevice.SOURCE_MOUSE) && motionView != null
                             && (overscrollMode == OVER_SCROLL_ALWAYS
@@ -4677,6 +4688,14 @@
         }
     }
 
+    private void initDifferentialFlingHelperIfNotExists() {
+        if (mDifferentialMotionFlingHelper == null) {
+            mDifferentialMotionFlingHelper =
+                    new DifferentialMotionFlingHelper(
+                            mContext, new DifferentialFlingTarget());
+        }
+    }
+
     private void recycleVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
@@ -8197,4 +8216,26 @@
             }
         }
     }
+
+    private class DifferentialFlingTarget
+            implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+        @Override
+        public boolean startDifferentialMotionFling(float velocity) {
+            stopDifferentialMotionFling();
+            fling((int) velocity);
+            return true;
+        }
+
+        @Override
+        public void stopDifferentialMotionFling() {
+            if (mFlingRunnable != null) {
+                mFlingRunnable.endFling();
+            }
+        }
+
+        @Override
+        public float getScaledScrollFactor() {
+            return -mVerticalScrollFactor;
+        }
+    }
 }
diff --git a/core/java/android/widget/DifferentialMotionFlingHelper.java b/core/java/android/widget/DifferentialMotionFlingHelper.java
new file mode 100644
index 0000000..95d24ec
--- /dev/null
+++ b/core/java/android/widget/DifferentialMotionFlingHelper.java
@@ -0,0 +1,249 @@
+/*
+ * 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.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper for controlling differential motion flings.
+ *
+ * <p><b>Differential motion</b> here refers to motions that report change in position instead of
+ * absolution position. For instance, differential data points of 2, -1, 5 represent: there was
+ * a movement by "2" units, then by "-1" units, then by "5" units. Examples of motions reported
+ * differentially include motions from {@link MotionEvent#AXIS_SCROLL}.
+ *
+ * <p>The client should call {@link #onMotionEvent} when a differential motion event happens on
+ * the target View (that is, the View on which we want to fling), and this class processes the event
+ * to orchestrate fling.
+ *
+ * <p>Note that this helper class currently works to control fling only in one direction at a time.
+ * As such, it works independently of horizontal/vertical orientations. It requests its client to
+ * start/stop fling, and it's up to the client to choose the fling direction based on its specific
+ * internal configurations and/or preferences.
+ *
+ * @hide
+ */
+public class DifferentialMotionFlingHelper {
+    private final Context mContext;
+    private final DifferentialMotionFlingTarget mTarget;
+
+    private final FlingVelocityThresholdCalculator mVelocityThresholdCalculator;
+    private final DifferentialVelocityProvider mVelocityProvider;
+
+    @Nullable private VelocityTracker mVelocityTracker;
+
+    private float mLastFlingVelocity;
+
+    private int mLastProcessedAxis = -1;
+    private int mLastProcessedSource = -1;
+    private int mLastProcessedDeviceId = -1;
+
+    // Initialize min and max to +infinity and 0, to effectively disable fling at start.
+    private final int[] mFlingVelocityThresholds = new int[] {Integer.MAX_VALUE, 0};
+
+    /** Interface to calculate the fling velocity thresholds. Helps fake during testing. */
+    @VisibleForTesting
+    public interface FlingVelocityThresholdCalculator {
+        /**
+         * Calculates the fling velocity thresholds (in pixels/second) and puts them in a provided
+         * store.
+         *
+         * @param context the context associated with the View that may be flung.
+         * @param store an at-least size-2 int array. The method will overwrite positions 0 and 1
+         *             with the min and max fling velocities, respectively.
+         * @param event the event that may trigger fling.
+         * @param axis the axis being processed for the event.
+         */
+        void calculateFlingVelocityThresholds(
+                Context context, int[] store, MotionEvent event, int axis);
+    }
+
+    /**
+     * Interface to provide velocity. Helps fake during testing.
+     *
+     * <p>The client should call {@link #getCurrentVelocity(VelocityTracker, MotionEvent, int)} each
+     * time it wants to consider a {@link MotionEvent} towards the latest velocity, and the
+     * interface handles providing velocity that accounts for the latest and all past events.
+     */
+    @VisibleForTesting
+    public interface DifferentialVelocityProvider {
+        /**
+         * Returns the latest velocity.
+         *
+         * @param vt the {@link VelocityTracker} to be used to compute velocity.
+         * @param event the latest event to be considered in the velocity computations.
+         * @param axis the axis being processed for the event.
+         * @return the calculated, latest velocity.
+         */
+        float getCurrentVelocity(VelocityTracker vt, MotionEvent event, int axis);
+    }
+
+    /**
+     * Represents an entity that may be flung by a differential motion or an entity that initiates
+     * fling on a target View.
+     */
+    public interface DifferentialMotionFlingTarget {
+        /**
+         * Start flinging on the target View by a given velocity.
+         *
+         * @param velocity the fling velocity, in pixels/second.
+         * @return {@code true} if fling was successfully initiated, {@code false} otherwise.
+         */
+        boolean startDifferentialMotionFling(float velocity);
+
+        /** Stop any ongoing fling on the target View that is caused by a differential motion. */
+        void stopDifferentialMotionFling();
+
+        /**
+         * Returns the scaled scroll factor to be used for differential motions. This is the
+         * value that the raw {@link MotionEvent} values should be multiplied with to get pixels.
+         *
+         * <p>This usually is one of the values provided by {@link ViewConfiguration}. It is
+         * up to the client to choose and provide any value as per its internal configuration.
+         *
+         * @see ViewConfiguration#getScaledHorizontalScrollFactor()
+         * @see ViewConfiguration#getScaledVerticalScrollFactor()
+         */
+        float getScaledScrollFactor();
+    }
+
+    /** Constructs an instance for a given {@link DifferentialMotionFlingTarget}. */
+    public DifferentialMotionFlingHelper(
+            Context context,
+            DifferentialMotionFlingTarget target) {
+        this(context,
+                target,
+                DifferentialMotionFlingHelper::calculateFlingVelocityThresholds,
+                DifferentialMotionFlingHelper::getCurrentVelocity);
+    }
+
+    @VisibleForTesting
+    public DifferentialMotionFlingHelper(
+            Context context,
+            DifferentialMotionFlingTarget target,
+            FlingVelocityThresholdCalculator velocityThresholdCalculator,
+            DifferentialVelocityProvider velocityProvider) {
+        mContext = context;
+        mTarget = target;
+        mVelocityThresholdCalculator = velocityThresholdCalculator;
+        mVelocityProvider = velocityProvider;
+    }
+
+    /**
+     * Called to report when a differential motion happens on the View that's the target for fling.
+     *
+     * @param event the {@link MotionEvent} being reported.
+     * @param axis the axis being processed by the target View.
+     */
+    public void onMotionEvent(MotionEvent event, int axis) {
+        boolean flingParamsChanged = calculateFlingVelocityThresholds(event, axis);
+        if (mFlingVelocityThresholds[0] == Integer.MAX_VALUE) {
+            // Integer.MAX_VALUE means that the device does not support fling for the current
+            // configuration. Do not proceed any further.
+            recycleVelocityTracker();
+            return;
+        }
+
+        float scaledVelocity =
+                getCurrentVelocity(event, axis) * mTarget.getScaledScrollFactor();
+
+        float velocityDirection = Math.signum(scaledVelocity);
+        // Stop ongoing fling if there has been state changes affecting fling, or if the current
+        // velocity (if non-zero) is opposite of the velocity that last caused fling.
+        if (flingParamsChanged
+                || (velocityDirection != Math.signum(mLastFlingVelocity)
+                    && velocityDirection != 0)) {
+            mTarget.stopDifferentialMotionFling();
+        }
+
+        if (Math.abs(scaledVelocity) < mFlingVelocityThresholds[0]) {
+            return;
+        }
+
+        // Clamp the scaled velocity between [-max, max].
+        // e.g. if max=100, and vel=200
+        // vel = max(-100, min(200, 100)) = max(-100, 100) = 100
+        // e.g. if max=100, and vel=-200
+        // vel = max(-100, min(-200, 100)) = max(-100, -200) = -100
+        scaledVelocity =
+                Math.max(
+                        -mFlingVelocityThresholds[1],
+                        Math.min(scaledVelocity, mFlingVelocityThresholds[1]));
+
+        boolean flung = mTarget.startDifferentialMotionFling(scaledVelocity);
+        mLastFlingVelocity = flung ? scaledVelocity : 0;
+    }
+
+    /**
+     * Calculates fling velocity thresholds based on the provided event and axis, and returns {@code
+     * true} if there has been a change of any params that may affect fling velocity thresholds.
+     */
+    private boolean calculateFlingVelocityThresholds(MotionEvent event, int axis) {
+        int source = event.getSource();
+        int deviceId = event.getDeviceId();
+        if (mLastProcessedSource != source
+                || mLastProcessedDeviceId != deviceId
+                || mLastProcessedAxis != axis) {
+            mVelocityThresholdCalculator.calculateFlingVelocityThresholds(
+                    mContext, mFlingVelocityThresholds, event, axis);
+            // Save data about this processing so that we don't have to re-process fling thresholds
+            // for similar parameters.
+            mLastProcessedSource = source;
+            mLastProcessedDeviceId = deviceId;
+            mLastProcessedAxis = axis;
+            return true;
+        }
+        return false;
+    }
+
+    private static void calculateFlingVelocityThresholds(
+            Context context, int[] buffer, MotionEvent event, int axis) {
+        int source = event.getSource();
+        int deviceId = event.getDeviceId();
+
+        ViewConfiguration vc = ViewConfiguration.get(context);
+        buffer[0] = vc.getScaledMinimumFlingVelocity(deviceId, axis, source);
+        buffer[1] = vc.getScaledMaximumFlingVelocity(deviceId, axis, source);
+    }
+
+    private float getCurrentVelocity(MotionEvent event, int axis) {
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+
+        return mVelocityProvider.getCurrentVelocity(mVelocityTracker, event, axis);
+    }
+
+    private void recycleVelocityTracker() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private static float getCurrentVelocity(VelocityTracker vt, MotionEvent event, int axis) {
+        vt.addMovement(event);
+        vt.computeCurrentVelocity(1000);
+        return vt.getAxisVelocity(axis);
+    }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 58f6613..f99f138 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8207,7 +8207,8 @@
          */
         void beforeSetText() {
             // TextView#setText is called because our call to
-            // TextView#setTransformationMethodInternal in enterInsertMode() or exitInsertMode().
+            // TextView#setTransformationMethodInternal in enterInsertMode(), exitInsertMode() or
+            // updateTransformationMethod().
             // Do nothing in this case.
             if (mUpdatingTransformationMethod) {
                 return;
@@ -8218,22 +8219,28 @@
         }
 
         /**
-         * 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.
+         * 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 oldTransformationMethod the new {@link TransformationMethod} to be set on the
+         * @param transformationMethod the new {@link TransformationMethod} to be set on the
          *                             TextView.
-         * @return the updated {@link TransformationMethod} to be set on the Textview.
          */
-        TransformationMethod updateTransformationMethod(
-                TransformationMethod oldTransformationMethod) {
-            if (!mIsInsertModeActive) return oldTransformationMethod;
+        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);
         }
     }
 
@@ -8259,18 +8266,11 @@
      * @param method the {@link TransformationMethod} to be set on the TextView.
      */
     void setTransformationMethod(TransformationMethod method) {
-        if (mInsertModeController == null || !mInsertModeController.mIsInsertModeActive) {
+        if (mInsertModeController == null) {
             mTextView.setTransformationMethodInternal(method, /* 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();
-        method = mInsertModeController.updateTransformationMethod(method);
-        mTextView.setTransformationMethodInternal(method, /* updateText */ true);
-        Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+        mInsertModeController.updateTransformationMethod(method);
     }
 
     /**
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index cb5dbe6..eeb6b43 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -204,6 +204,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private StrictMode.Span mFlingStrictSpan = null;
 
+    private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+
     /**
      * Sentinel value for no current active pointer.
      * Used by {@link #mActivePointerId}.
@@ -594,6 +596,14 @@
         }
     }
 
+    private void initDifferentialFlingHelperIfNotExists() {
+        if (mDifferentialMotionFlingHelper == null) {
+            mDifferentialMotionFlingHelper =
+                    new DifferentialMotionFlingHelper(
+                            mContext, new DifferentialFlingTarget());
+        }
+    }
+
     private void recycleVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
@@ -942,17 +952,22 @@
     public boolean onGenericMotionEvent(MotionEvent event) {
         switch (event.getAction()) {
             case MotionEvent.ACTION_SCROLL:
-                final float axisValue;
+                final int axis;
                 if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
-                    axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
+                    axis = MotionEvent.AXIS_VSCROLL;
                 } else if (event.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER)) {
-                    axisValue = event.getAxisValue(MotionEvent.AXIS_SCROLL);
+                    axis = MotionEvent.AXIS_SCROLL;
                 } else {
-                    axisValue = 0;
+                    axis = -1;
                 }
 
+                final float axisValue = (axis == -1) ? 0 : event.getAxisValue(axis);
                 final int delta = Math.round(axisValue * mVerticalScrollFactor);
                 if (delta != 0) {
+                    // Tracks whether or not we should attempt fling for this event.
+                    // Fling should not be attempted if the view is already at the limit of scroll,
+                    // since it conflicts with EdgeEffect.
+                    boolean shouldAttemptFling = true;
                     final int range = getScrollRange();
                     int oldScrollY = mScrollY;
                     int newScrollY = oldScrollY - delta;
@@ -971,6 +986,7 @@
                             absorbed = true;
                         }
                         newScrollY = 0;
+                        shouldAttemptFling = false;
                     } else if (newScrollY > range) {
                         if (canOverscroll) {
                             mEdgeGlowBottom.onPullDistance(
@@ -980,9 +996,14 @@
                             absorbed = true;
                         }
                         newScrollY = range;
+                        shouldAttemptFling = false;
                     }
                     if (newScrollY != oldScrollY) {
                         super.scrollTo(mScrollX, newScrollY);
+                        if (shouldAttemptFling) {
+                            initDifferentialFlingHelperIfNotExists();
+                            mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
+                        }
                         return true;
                     }
                     if (absorbed) {
@@ -2126,4 +2147,23 @@
         };
     }
 
+    private class DifferentialFlingTarget
+            implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+        @Override
+        public boolean startDifferentialMotionFling(float velocity) {
+            stopDifferentialMotionFling();
+            fling((int) velocity);
+            return true;
+        }
+
+        @Override
+        public void stopDifferentialMotionFling() {
+            mScroller.abortAnimation();
+        }
+
+        @Override
+        public float getScaledScrollFactor() {
+            return -mVerticalScrollFactor;
+        }
+    }
 }
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 99e63ec..c9ac245 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -86,6 +86,7 @@
      * @param token The token used to attach to a window manager node. It is usually from
      *              {@link Context#getWindowContextToken()}.
      */
+    @VisibleForTesting
     public WindowContextController(@NonNull WindowTokenClient token) {
         mToken = token;
     }
@@ -104,7 +105,7 @@
             throw new IllegalStateException("A Window Context can be only attached to "
                     + "a DisplayArea once.");
         }
-        mAttachedToDisplayArea = WindowTokenClientController.getInstance().attachToDisplayArea(
+        mAttachedToDisplayArea = getWindowTokenClientController().attachToDisplayArea(
                 mToken, type, displayId, options)
                 ? AttachStatus.STATUS_ATTACHED : AttachStatus.STATUS_FAILED;
         if (mAttachedToDisplayArea == AttachStatus.STATUS_FAILED) {
@@ -136,22 +137,31 @@
      * @see WindowProviderService#attachToWindowToken(IBinder))
      * @see IWindowManager#attachWindowContextToWindowToken
      */
-    public void attachToWindowToken(IBinder windowToken) {
+    public void attachToWindowToken(@NonNull IBinder windowToken) {
         if (mAttachedToDisplayArea != AttachStatus.STATUS_ATTACHED) {
             throw new IllegalStateException("The Window Context should have been attached"
                     + " to a DisplayArea. AttachToDisplayArea:" + mAttachedToDisplayArea);
         }
-        WindowTokenClientController.getInstance().attachToWindowToken(mToken, windowToken);
+        if (!getWindowTokenClientController().attachToWindowToken(mToken, windowToken)) {
+            Log.e(TAG, "attachToWindowToken fail");
+        }
     }
 
     /** Detaches the window context from the node it's currently associated with. */
     public void detachIfNeeded() {
         if (mAttachedToDisplayArea == AttachStatus.STATUS_ATTACHED) {
-            WindowTokenClientController.getInstance().detachIfNeeded(mToken);
+            getWindowTokenClientController().detachIfNeeded(mToken);
             mAttachedToDisplayArea = AttachStatus.STATUS_DETACHED;
             if (DEBUG_ATTACH) {
                 Log.d(TAG, "Detach Window Context.");
             }
         }
     }
+
+    /** Gets the {@link WindowTokenClientController}. */
+    @VisibleForTesting
+    @NonNull
+    public WindowTokenClientController getWindowTokenClientController() {
+        return WindowTokenClientController.getInstance();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt b/core/java/android/window/WindowContextInfo.aidl
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
rename to core/java/android/window/WindowContextInfo.aidl
index 64f5087..360431c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerNames.kt
+++ b/core/java/android/window/WindowContextInfo.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.scene.shared.model
+package android.window;
 
-object SceneContainerNames {
-    const val SYSTEM_UI_DEFAULT = "system_ui"
-}
+/** @hide */
+parcelable WindowContextInfo;
\ No newline at end of file
diff --git a/core/java/android/window/WindowContextInfo.java b/core/java/android/window/WindowContextInfo.java
new file mode 100644
index 0000000..3c21cd4
--- /dev/null
+++ b/core/java/android/window/WindowContextInfo.java
@@ -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 android.window;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Stores information about a particular window that a {@link WindowContext} is attached to.
+ * @hide
+ */
+public class WindowContextInfo implements Parcelable {
+
+    /**
+     * Configuration of the window.
+     */
+    @NonNull
+    private final Configuration mConfiguration;
+
+    /**
+     * The display id that the window is attached to.
+     */
+    private final int mDisplayId;
+
+    public WindowContextInfo(@NonNull Configuration configuration, int displayId) {
+        mConfiguration = requireNonNull(configuration);
+        mDisplayId = displayId;
+    }
+
+    @NonNull
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    // Parcelable implementation
+
+    /** Writes to Parcel. */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mConfiguration, flags);
+        dest.writeInt(mDisplayId);
+    }
+
+    /** Reads from Parcel. */
+    private WindowContextInfo(@NonNull Parcel in) {
+        mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mDisplayId = in.readInt();
+    }
+
+    public static final @NonNull Creator<WindowContextInfo> CREATOR = new Creator<>() {
+        public WindowContextInfo createFromParcel(Parcel in) {
+            return new WindowContextInfo(in);
+        }
+
+        public WindowContextInfo[] newArray(int size) {
+            return new WindowContextInfo[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final WindowContextInfo other = (WindowContextInfo) o;
+        return Objects.equals(mConfiguration, other.mConfiguration)
+                && mDisplayId == other.mDisplayId;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + Objects.hashCode(mConfiguration);
+        result = 31 * result + mDisplayId;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "WindowContextInfo{config=" + mConfiguration
+                + ", displayId=" + mDisplayId
+                + "}";
+    }
+}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 7458563..6a32529 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -99,6 +99,13 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
         // TODO(b/290876897): No need to post on mHandler after migrating to ClientTransaction
+        postOnConfigurationChanged(newConfig, newDisplayId);
+    }
+
+    /**
+     * Posts an {@link #onConfigurationChanged} to the main thread.
+     */
+    public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
     }
@@ -162,7 +169,6 @@
                 windowContext.dispatchConfigurationChanged(newConfig);
             }
 
-
             if (shouldReportConfigChange && diff != 0
                     && context instanceof WindowProviderService) {
                 final WindowProviderService windowProviderService = (WindowProviderService) context;
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index 4484707..7a84123 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -17,22 +17,21 @@
 package android.window;
 
 import static android.view.WindowManager.LayoutParams.WindowType;
-import static android.view.WindowManagerGlobal.getWindowManagerService;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
-import android.app.servertransaction.WindowContextConfigurationChangeItem;
+import android.app.servertransaction.WindowContextInfoChangeItem;
 import android.app.servertransaction.WindowContextWindowRemovalItem;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -56,6 +55,7 @@
     private final ArrayMap<IBinder, WindowTokenClient> mWindowTokenClientMap = new ArrayMap<>();
 
     /** Gets the singleton controller. */
+    @NonNull
     public static WindowTokenClientController getInstance() {
         synchronized (WindowTokenClientController.class) {
             if (sController == null) {
@@ -75,6 +75,7 @@
 
     /** Creates a new instance for test only. */
     @VisibleForTesting
+    @NonNull
     public static WindowTokenClientController createInstanceForTesting() {
         return new WindowTokenClientController();
     }
@@ -92,17 +93,17 @@
      */
     public boolean attachToDisplayArea(@NonNull WindowTokenClient client,
             @WindowType int type, int displayId, @Nullable Bundle options) {
-        final Configuration configuration;
+        final WindowContextInfo info;
         try {
-            configuration = getWindowManagerService().attachWindowContextToDisplayArea(
+            info = getWindowManagerService().attachWindowContextToDisplayArea(
                     mAppThread, client, type, displayId, options);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        if (configuration == null) {
+        if (info == null) {
             return false;
         }
-        onWindowContextTokenAttached(client, displayId, configuration);
+        onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
         return true;
     }
 
@@ -119,16 +120,16 @@
         if (wms == null) {
             return false;
         }
-        final Configuration configuration;
+        final WindowContextInfo info;
         try {
-            configuration = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
+            info = wms.attachWindowContextToDisplayContent(mAppThread, client, displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        if (configuration == null) {
+        if (info == null) {
             return false;
         }
-        onWindowContextTokenAttached(client, displayId, configuration);
+        onWindowContextTokenAttached(client, info, false /* shouldReportConfigChange */);
         return true;
     }
 
@@ -137,19 +138,23 @@
      *
      * @param client The {@link WindowTokenClient} to attach.
      * @param windowToken the window token to associated with
+     * @return {@code true} if attaching successfully.
      */
-    public void attachToWindowToken(@NonNull WindowTokenClient client,
+    public boolean attachToWindowToken(@NonNull WindowTokenClient client,
             @NonNull IBinder windowToken) {
+        final WindowContextInfo info;
         try {
-            getWindowManagerService().attachWindowContextToWindowToken(
+            info = getWindowManagerService().attachWindowContextToWindowToken(
                     mAppThread, client, windowToken);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
-        // We don't report configuration change for now.
-        synchronized (mLock) {
-            mWindowTokenClientMap.put(client.asBinder(), client);
+        if (info == null) {
+            return false;
         }
+        // We currently report configuration for WindowToken after attached.
+        onWindowContextTokenAttached(client, info, true /* shouldReportConfigChange */);
+        return true;
     }
 
     /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
@@ -166,21 +171,30 @@
         }
     }
 
-    private void onWindowContextTokenAttached(@NonNull WindowTokenClient client, int displayId,
-            @NonNull Configuration configuration) {
+    private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
+            @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
         synchronized (mLock) {
             mWindowTokenClientMap.put(client.asBinder(), client);
         }
-        client.onConfigurationChanged(configuration, displayId,
-                false /* shouldReportConfigChange */);
+        if (shouldReportConfigChange) {
+            // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the
+            // dispatch in the next loop to prevent the callback from being dispatched before
+            // #onCreate or WindowContext creation..
+            client.postOnConfigurationChanged(info.getConfiguration(), info.getDisplayId());
+        } else {
+            // Apply the config change directly in case users get stale values after WindowContext
+            // creation.
+            client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId(),
+                    false /* shouldReportConfigChange */);
+        }
     }
 
-    /** Called when receives {@link WindowContextConfigurationChangeItem}. */
-    public void onWindowContextConfigurationChanged(@NonNull IBinder clientToken,
-            @NonNull Configuration configuration, int displayId) {
+    /** Called when receives {@link WindowContextInfoChangeItem}. */
+    public void onWindowContextInfoChanged(@NonNull IBinder clientToken,
+            @NonNull WindowContextInfo info) {
         final WindowTokenClient windowTokenClient = getWindowTokenClient(clientToken);
         if (windowTokenClient != null) {
-            windowTokenClient.onConfigurationChanged(configuration, displayId);
+            windowTokenClient.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
         }
     }
 
@@ -203,4 +217,11 @@
         }
         return windowTokenClient;
     }
+
+    /** Gets the {@link IWindowManager}. */
+    @VisibleForTesting
+    @Nullable
+    public IWindowManager getWindowManagerService() {
+        return WindowManagerGlobal.getWindowManagerService();
+    }
 }
diff --git a/core/java/com/android/internal/infra/TEST_MAPPING b/core/java/com/android/internal/infra/TEST_MAPPING
index 3de107e..ddfd0ed 100644
--- a/core/java/com/android/internal/infra/TEST_MAPPING
+++ b/core/java/com/android/internal/infra/TEST_MAPPING
@@ -1,14 +1,22 @@
 {
   "presubmit": [
     {
-      "name": "CtsRoleTestCases"
+      "name": "CtsRoleTestCases",
+      "options": [
+          {
+              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          }
+      ]
     },
     {
       "name": "CtsPermissionTestCases",
       "options": [
-        {
-          "include-filter": "android.permission.cts.PermissionControllerTest"
-        }
+          {
+            "include-filter": "android.permission.cts.PermissionControllerTest"
+          },
+          {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          }
       ]
     },
     {
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 8fc30d1..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
                          jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
                                                      packageNameChars.c_str(), features);
 }
 
@@ -118,7 +118,7 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index d8a2497..ffe844d 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -41,7 +41,7 @@
 typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_closeSession)(APerformanceHintSession* session);
 typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
-typedef void (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
+typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
 typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
 
 bool gAPerformanceHintBindingInitialized = false;
@@ -112,6 +112,20 @@
 
 } // namespace
 
+static void throwExceptionForErrno(JNIEnv* env, int err, const std::string& msg) {
+    switch (err) {
+        case EINVAL:
+            jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", msg.c_str());
+            break;
+        case EPERM:
+            jniThrowExceptionFmt(env, "java/lang/SecurityException", msg.c_str());
+            break;
+        default:
+            jniThrowException(env, "java/lang/RuntimeException", msg.c_str());
+            break;
+    }
+}
+
 static jlong nativeAcquireManager(JNIEnv* env, jclass clazz) {
     ensureAPerformanceHintBindingInitialized();
     return reinterpret_cast<jlong>(gAPH_getManagerFn());
@@ -174,8 +188,11 @@
     for (size_t i = 0; i < tidsArray.size(); ++i) {
         tidsVector.push_back(static_cast<int32_t>(tidsArray[i]));
     }
-    gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
-                      tidsVector.data(), tidsVector.size());
+    int err = gAPH_setThreadsFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                tidsVector.data(), tidsVector.size());
+    if (err != 0) {
+        throwExceptionForErrno(env, err, "Failed to set threads for hint session");
+    }
 }
 
 // This call should only be used for validation in tests only. This call will initiate two IPC
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index baa47da..b29a4e6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5631,6 +5631,12 @@
                 android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
+    <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+         Allows an application to send and receive messages via CDM transports.
+    -->
+    <permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
+        android:protectionLevel="signature|module" />
+
     <!-- Allows an application to create new companion device associations.
          @SystemApi
          @hide -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 54dff08..7300772 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3938,8 +3938,14 @@
     <!-- Flag indicating device support for EAP SIM, AKA, AKA' -->
     <bool name="config_eap_sim_based_auth_supported">true</bool>
 
+    <!-- How long history of recent vibrations should be kept for the dumpsys. -->
+    <integer name="config_recentVibrationsDumpSizeLimit">20</integer>
+
     <!-- How long history of previous vibrations should be kept for the dumpsys. -->
-    <integer name="config_previousVibrationsDumpLimit">50</integer>
+    <integer name="config_previousVibrationsDumpSizeLimit">50</integer>
+
+    <!-- How close vibration request should be when they're aggregated for dumpsys, in ms. -->
+    <integer name="config_previousVibrationsDumpAggregationTimeMillisLimit">1000</integer>
 
     <!-- The default vibration strength, must be between 1 and 255 inclusive. -->
     <integer name="config_defaultVibrationAmplitude">255</integer>
@@ -5559,10 +5565,6 @@
     <!-- pdp data reject retry delay in ms -->
     <integer name="config_pdp_reject_retry_delay_ms">-1</integer>
 
-    <!-- Duration in milliseconds for device to vibrate on mash press on power
-         button. -->
-    <integer name="config_mashPressVibrateTimeOnPowerButton">0</integer>
-
     <!-- Whether or not to enable the binder heavy hitter watcher by default -->
     <bool name="config_defaultBinderHeavyHitterWatcherEnabled">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9da0f17..391d7bd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2038,7 +2038,9 @@
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
   <java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
-  <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+  <java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" />
+  <java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" />
+  <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
   <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
@@ -4947,8 +4949,6 @@
   <java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
   <java-symbol type="array" name="config_gnssParameters" />
 
-  <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" />
-
   <java-symbol type="string" name="config_systemGameService" />
 
   <java-symbol type="string" name="config_supervisedUserCreationPackage"/>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 8da6d74..c904d96 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -64,6 +64,7 @@
 import android.util.MergedConfiguration;
 import android.view.Display;
 import android.view.View;
+import android.window.WindowContextInfo;
 import android.window.WindowTokenClientController;
 
 import androidx.test.filters.MediumTest;
@@ -753,13 +754,12 @@
         WindowTokenClientController.overrideForTesting(windowTokenClientController);
         final IBinder clientToken = mock(IBinder.class);
         final Configuration configuration = new Configuration();
+        final WindowContextInfo info = new WindowContextInfo(configuration, DEFAULT_DISPLAY);
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> activityThread
-                .handleWindowContextConfigurationChanged(
-                        clientToken, configuration, DEFAULT_DISPLAY));
+                .handleWindowContextInfoChanged(clientToken, info));
 
-        verify(windowTokenClientController).onWindowContextConfigurationChanged(
-                clientToken, configuration, DEFAULT_DISPLAY);
+        verify(windowTokenClientController).onWindowContextInfoChanged(clientToken, info);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
similarity index 79%
rename from core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
rename to core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
index 7811e1a..37a517e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/WindowContextConfigurationChangeItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/WindowContextInfoChangeItemTest.java
@@ -23,6 +23,7 @@
 import android.app.ClientTransactionHandler;
 import android.content.res.Configuration;
 import android.os.IBinder;
+import android.window.WindowContextInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -30,12 +31,12 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * Tests for {@link WindowContextConfigurationChangeItem}.
+ * Tests for {@link WindowContextInfoChangeItem}.
  *
  * Build/Install/Run:
- *  atest FrameworksCoreTests:WindowContextConfigurationChangeItemTest
+ *  atest FrameworksCoreTests:WindowContextInfoChangeItemTest
  */
-public class WindowContextConfigurationChangeItemTest {
+public class WindowContextInfoChangeItemTest {
 
     @Mock
     private ClientTransactionHandler mHandler;
@@ -55,11 +56,11 @@
 
     @Test
     public void testExecute() {
-        final WindowContextConfigurationChangeItem item = WindowContextConfigurationChangeItem
+        final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
                 .obtain(mClientToken, mConfiguration, DEFAULT_DISPLAY);
         item.execute(mHandler, mToken, mPendingActions);
 
-        verify(mHandler).handleWindowContextConfigurationChanged(mClientToken, mConfiguration,
-                DEFAULT_DISPLAY);
+        verify(mHandler).handleWindowContextInfoChanged(mClientToken,
+                new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY));
     }
 }
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 7eefbbc..2c03fdc 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -139,11 +139,20 @@
     }
 
     @Test
-    public void testSetThreadsWithIllegalArgument() {
+    public void testSetThreads_emptyTids() {
         Session session = createSession();
         assumeNotNull(session);
         assertThrows(IllegalArgumentException.class, () -> {
-            session.setThreads(new int[] { });
+            session.setThreads(new int[]{});
+        });
+    }
+
+    @Test
+    public void testSetThreads_invalidTids() {
+        Session session = createSession();
+        assumeNotNull(session);
+        assertThrows(SecurityException.class, () -> {
+            session.setThreads(new int[]{-1});
         });
     }
 }
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/widget/DifferentialMotionFlingHelperTest.java b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
new file mode 100644
index 0000000..51c8bc0
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/DifferentialMotionFlingHelperTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.view.MotionEvent;
+
+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.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DifferentialMotionFlingHelperTest {
+    private int mMinVelocity = 0;
+    private int mMaxVelocity = Integer.MAX_VALUE;
+    /** A fake velocity value that's going to be returned from the velocity provider. */
+    private float mVelocity;
+    private boolean mVelocityCalculated;
+
+    private final DifferentialMotionFlingHelper.DifferentialVelocityProvider mVelocityProvider =
+            (vt, event, axis) -> {
+                mVelocityCalculated = true;
+                return mVelocity;
+            };
+
+    private final DifferentialMotionFlingHelper.FlingVelocityThresholdCalculator
+            mVelocityThresholdCalculator =
+                    (ctx, buffer, event, axis) -> {
+                        buffer[0] = mMinVelocity;
+                        buffer[1] = mMaxVelocity;
+                    };
+
+    private final TestDifferentialMotionFlingTarget mFlingTarget =
+            new TestDifferentialMotionFlingTarget();
+
+    private DifferentialMotionFlingHelper mFlingHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        mFlingHelper = new DifferentialMotionFlingHelper(
+                ApplicationProvider.getApplicationContext(),
+                mFlingTarget,
+                mVelocityThresholdCalculator,
+                mVelocityProvider);
+    }
+
+    @Test
+    public void deviceDoesNotSupportFling_noVelocityCalculated() {
+        mMinVelocity = Integer.MAX_VALUE;
+        mMaxVelocity = Integer.MIN_VALUE;
+
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
+
+        assertFalse(mVelocityCalculated);
+    }
+
+    @Test
+    public void flingVelocityOppositeToPrevious_stopsOngoingFling() {
+        deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 50);
+        deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, -10);
+
+        // One stop on the initial event, and second stop due to opposite velocities.
+        assertEquals(2, mFlingTarget.mNumStops);
+    }
+
+    @Test
+    public void flingParamsChanged_stopsOngoingFling() {
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 50);
+        deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 10);
+
+        // One stop on the initial event, and second stop due to changed axis/source.
+        assertEquals(2, mFlingTarget.mNumStops);
+    }
+
+    @Test
+    public void positiveFlingVelocityTooLow_doesNotGenerateFling() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 20);
+
+        assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
+    public void negativeFlingVelocityTooLow_doesNotGenerateFling() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -20);
+
+        assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
+    public void positiveFlingVelocityAboveMinimum_generateFlings() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
+
+        assertEquals(60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
+    public void negativeFlingVelocityAboveMinimum_generateFlings() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -60);
+
+        assertEquals(-60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
+    public void positiveFlingVelocityAboveMaximum_velocityClamped() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 3000);
+
+        assertEquals(100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    @Test
+    public void negativeFlingVelocityAboveMaximum_velocityClamped() {
+        mMinVelocity = 50;
+        mMaxVelocity = 100;
+        deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -3000);
+
+        assertEquals(-100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
+    }
+
+    private MotionEvent createRotaryEncoderEvent() {
+        return MotionEventUtils.createRotaryEvent(-2);
+    }
+
+    private MotionEvent createPointerEvent() {
+        return MotionEventUtils.createGenericPointerEvent(/* hScroll= */ 0, /* vScroll= */ -1);
+
+    }
+
+    private void deliverEventWithVelocity(MotionEvent ev, int axis, float velocity) {
+        mVelocity = velocity;
+        mFlingHelper.onMotionEvent(ev, axis);
+        ev.recycle();
+    }
+
+    private static class TestDifferentialMotionFlingTarget
+            implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
+        float mLastFlingVelocity = 0;
+        int mNumStops = 0;
+
+        @Override
+        public boolean startDifferentialMotionFling(float velocity) {
+            mLastFlingVelocity = velocity;
+            return true;
+        }
+
+        @Override
+        public void stopDifferentialMotionFling() {
+            mNumStops++;
+        }
+
+        @Override
+        public float getScaledScrollFactor() {
+            return 1;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/MotionEventUtils.java b/core/tests/coretests/src/android/widget/MotionEventUtils.java
new file mode 100644
index 0000000..275efa3
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/MotionEventUtils.java
@@ -0,0 +1,69 @@
+/*
+ * 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.widget;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
+import static android.view.MotionEvent.ACTION_SCROLL;
+import static android.view.MotionEvent.AXIS_HSCROLL;
+import static android.view.MotionEvent.AXIS_SCROLL;
+import static android.view.MotionEvent.AXIS_VSCROLL;
+
+import android.view.MotionEvent;
+
+/** Test utilities for {@link MotionEvent}s. */
+public class MotionEventUtils {
+
+    /** Creates a test {@link MotionEvent} from a {@link SOURCE_ROTARY_ENCODER}. */
+    public static MotionEvent createRotaryEvent(float scroll) {
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.setAxisValue(AXIS_SCROLL, scroll);
+
+        return createGenericMotionEvent(SOURCE_ROTARY_ENCODER, ACTION_SCROLL, coords);
+    }
+
+    /** Creates a test {@link MotionEvent} from a {@link SOURCE_CLASS_POINTER}. */
+    public static MotionEvent createGenericPointerEvent(float hScroll, float vScroll) {
+        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+        coords.setAxisValue(AXIS_HSCROLL, hScroll);
+        coords.setAxisValue(AXIS_VSCROLL, vScroll);
+
+        return createGenericMotionEvent(SOURCE_CLASS_POINTER, ACTION_SCROLL, coords);
+    }
+
+    private static MotionEvent createGenericMotionEvent(
+            int source, int action, MotionEvent.PointerCoords coords) {
+        MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
+        props.id = 0;
+
+        return MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 100,
+                action,
+                /* pointerCount= */ 1,
+                new MotionEvent.PointerProperties[] {props},
+                new MotionEvent.PointerCoords[] {coords},
+                /* metaState= */ 0,
+                /* buttonState= */ 0,
+                /* xPrecision= */ 0,
+                /* yPrecision= */ 0,
+                /* deviceId= */ 1,
+                /* edgeFlags= */ 0,
+                source,
+                /* flags= */ 0);
+    }
+}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 940c067..30c0f2b 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.os.Binder;
@@ -36,7 +37,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -62,24 +62,16 @@
     @Mock
     private WindowTokenClient mMockToken;
 
-    private WindowTokenClientController mOriginalController;
-
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mController = new WindowContextController(mMockToken);
+        mController = spy(new WindowContextController(mMockToken));
+        doReturn(mWindowTokenClientController).when(mController).getWindowTokenClientController();
         doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt(), anyBoolean());
-        mOriginalController = WindowTokenClientController.getInstance();
-        WindowTokenClientController.overrideForTesting(mWindowTokenClientController);
         doReturn(true).when(mWindowTokenClientController).attachToDisplayArea(
                 eq(mMockToken), anyInt(), anyInt(), any());
     }
 
-    @After
-    public void tearDown() {
-        WindowTokenClientController.overrideForTesting(mOriginalController);
-    }
-
     @Test(expected = IllegalStateException.class)
     public void testAttachToDisplayAreaTwiceThrowException() {
         mController.attachToDisplayArea(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 767dd8c..7bd6f05 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -35,11 +35,9 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
 
 import androidx.test.filters.SmallTest;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -66,22 +64,16 @@
     // Can't mock final class.
     private final Configuration mConfiguration = new Configuration();
 
-    private IWindowManager mOriginalWindowManagerService;
-
+    private WindowContextInfo mWindowContextInfo;
     private WindowTokenClientController mController;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mOriginalWindowManagerService = WindowManagerGlobal.getWindowManagerService();
-        WindowManagerGlobal.overrideWindowManagerServiceForTesting(mWindowManagerService);
         doReturn(mClientToken).when(mWindowTokenClient).asBinder();
         mController = spy(WindowTokenClientController.createInstanceForTesting());
-    }
-
-    @After
-    public void tearDown() {
-        WindowManagerGlobal.overrideWindowManagerServiceForTesting(mOriginalWindowManagerService);
+        doReturn(mWindowManagerService).when(mController).getWindowManagerService();
+        mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY);
     }
 
     @Test
@@ -96,7 +88,7 @@
                 TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY, null /* options */);
         verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
 
-        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+        doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToDisplayArea(
                 any(), any(), anyInt(), anyInt(), any());
 
         assertTrue(mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
@@ -119,13 +111,13 @@
 
         verify(mWindowManagerService, never()).detachWindowContext(any());
 
-        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayArea(
+        doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToDisplayArea(
                 any(), any(), anyInt(), anyInt(), any());
         mController.attachToDisplayArea(mWindowTokenClient, TYPE_APPLICATION_OVERLAY,
                 DEFAULT_DISPLAY, null /* options */);
         mController.detachIfNeeded(mWindowTokenClient);
 
-        verify(mWindowManagerService).detachWindowContext(any());
+        verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
     }
 
     @Test
@@ -139,8 +131,8 @@
                 DEFAULT_DISPLAY);
         verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
 
-        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
-                any(), any(), anyInt());
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToDisplayContent(any(), any(), anyInt());
 
         assertTrue(mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY));
         verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY,
@@ -160,21 +152,30 @@
 
         verify(mWindowManagerService, never()).detachWindowContext(any());
 
-        doReturn(mConfiguration).when(mWindowManagerService).attachWindowContextToDisplayContent(
-                any(), any(), anyInt());
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToDisplayContent(any(), any(), anyInt());
         mController.attachToDisplayContent(mWindowTokenClient, DEFAULT_DISPLAY);
         mController.detachIfNeeded(mWindowTokenClient);
 
-        verify(mWindowManagerService).detachWindowContext(any());
+        verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
     }
 
     @Test
     public void testAttachToWindowToken() throws RemoteException {
-        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+        doReturn(null).when(mWindowManagerService).attachWindowContextToWindowToken(
+                any(), any(), any());
 
+        assertFalse(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
         verify(mWindowManagerService).attachWindowContextToWindowToken(
                 ActivityThread.currentActivityThread().getApplicationThread(), mWindowTokenClient,
                 mWindowToken);
+        verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt(), anyBoolean());
+
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToWindowToken(any(), any(), any());
+
+        assertTrue(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
+        verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
     }
 
     @Test
@@ -183,35 +184,59 @@
 
         verify(mWindowManagerService, never()).detachWindowContext(any());
 
+        doReturn(null).when(mWindowManagerService).attachWindowContextToWindowToken(
+                any(), any(), any());
         mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
         mController.detachIfNeeded(mWindowTokenClient);
 
-        verify(mWindowManagerService).detachWindowContext(any());
+        verify(mWindowManagerService, never()).detachWindowContext(any());
+
+        doReturn(mWindowContextInfo).when(mWindowManagerService).attachWindowContextToWindowToken(
+                any(), any(), any());
+        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+        mController.detachIfNeeded(mWindowTokenClient);
+
+        verify(mWindowManagerService).detachWindowContext(mWindowTokenClient);
     }
 
     @Test
-    public void testOnWindowContextConfigurationChanged() {
-        mController.onWindowContextConfigurationChanged(
-                mClientToken, mConfiguration, DEFAULT_DISPLAY);
+    public void testOnWindowContextInfoChanged() throws RemoteException {
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToWindowToken(any(), any(), any());
+
+        // No invoke if not attached.
+        mController.onWindowContextInfoChanged(mClientToken, mWindowContextInfo);
 
         verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt());
 
-        mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
+        // Invoke postOnConfigurationChanged when attached
+        assertTrue(mController.attachToWindowToken(mWindowTokenClient, mWindowToken));
 
-        mController.onWindowContextConfigurationChanged(
-                mClientToken, mConfiguration, DEFAULT_DISPLAY);
+        verify(mWindowTokenClient).postOnConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
 
-        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY);
+        // Invoke onConfigurationChanged when onWindowContextInfoChanged
+        mController.onWindowContextInfoChanged(
+                mClientToken, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1));
+
+        verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
     }
 
     @Test
-    public void testOnWindowContextWindowRemoved() {
+    public void testOnWindowContextWindowRemoved() throws RemoteException {
+        doReturn(mWindowContextInfo).when(mWindowManagerService)
+                .attachWindowContextToWindowToken(any(), any(), any());
+
+        // No invoke if not attached.
         mController.onWindowContextWindowRemoved(mClientToken);
 
         verify(mWindowTokenClient, never()).onWindowTokenRemoved();
 
+        // No invoke if not onWindowTokenRemoved.
         mController.attachToWindowToken(mWindowTokenClient, mWindowToken);
 
+        verify(mWindowTokenClient, never()).onWindowTokenRemoved();
+
+        // Invoke onWindowTokenRemoved when onWindowContextWindowRemoved
         mController.onWindowContextWindowRemoved(mClientToken);
 
         verify(mWindowTokenClient).onWindowTokenRemoved();
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 3231192..8268077 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -45,7 +45,6 @@
                 VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
 
         assertEquals(-1, prebaked.getDuration());
-        assertTrue(prebaked.hasNonZeroAmplitude());
         assertEquals(VibrationEffect.EFFECT_CLICK, prebaked.getEffectId());
         assertEquals(VibrationEffect.EFFECT_STRENGTH_MEDIUM, prebaked.getEffectStrength());
         assertTrue(prebaked.shouldFallback());
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index 955d6ac..6f5adcd 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -46,7 +46,6 @@
                 VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 10);
 
         assertEquals(-1, primitive.getDuration());
-        assertTrue(primitive.hasNonZeroAmplitude());
         assertEquals(VibrationEffect.Composition.PRIMITIVE_CLICK, primitive.getPrimitiveId());
         assertEquals(10, primitive.getDelay());
         assertEquals(1f, primitive.getScale(), TOLERANCE);
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index dcbb56e..68870e5 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -54,7 +54,6 @@
                 /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 100);
 
         assertEquals(100L, ramp.getDuration());
-        assertTrue(ramp.hasNonZeroAmplitude());
         assertEquals(1f, ramp.getStartAmplitude());
         assertEquals(0f, ramp.getEndAmplitude());
         assertEquals(100f, ramp.getStartFrequencyHz());
@@ -96,13 +95,6 @@
     }
 
     @Test
-    public void testHasNonZeroAmplitude() {
-        assertTrue(new RampSegment(0, 1, 0, 0, 0).hasNonZeroAmplitude());
-        assertTrue(new RampSegment(0.01f, 0, 0, 0, 0).hasNonZeroAmplitude());
-        assertFalse(new RampSegment(0, 0, 0, 0, 0).hasNonZeroAmplitude());
-    }
-
-    @Test
     public void testResolve() {
         RampSegment ramp = new RampSegment(0, 1, 0, 0, 0);
         assertSame(ramp, ramp.resolve(100));
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index f9f1c08..34bb892 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -52,7 +52,6 @@
                 /* duration= */ 100);
 
         assertEquals(100, step.getDuration());
-        assertTrue(step.hasNonZeroAmplitude());
         assertEquals(1f, step.getAmplitude());
         assertEquals(1f, step.getFrequencyHz());
     }
@@ -87,14 +86,6 @@
     }
 
     @Test
-    public void testHasNonZeroAmplitude() {
-        assertTrue(new StepSegment(1f, 0, 0).hasNonZeroAmplitude());
-        assertTrue(new StepSegment(0.01f, 0, 0).hasNonZeroAmplitude());
-        assertTrue(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0).hasNonZeroAmplitude());
-        assertFalse(new StepSegment(0, 0, 0).hasNonZeroAmplitude());
-    }
-
-    @Test
     public void testResolve() {
         StepSegment original = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
         assertEquals(1f, original.resolve(VibrationEffect.MAX_AMPLITUDE).getAmplitude());
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 43683ff..c6a9033 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -87,5 +87,6 @@
         <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
         <permission name="android.permission.READ_SEARCH_INDEXABLES" />
         <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+        <permission name="android.permission.QUERY_CLONED_APPS"/>
     </privapp-permissions>
 </permissions>
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 8bd500e..e9abc7e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -150,6 +150,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/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/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e69825f2..66b9ade6 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
@@ -821,13 +821,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);
+            }
         }
     }
 
@@ -1053,6 +1057,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)
@@ -1438,6 +1456,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);
@@ -2099,14 +2128,25 @@
         }
 
         @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
         public void collapseBubbles() {
             mMainExecutor.execute(() -> mController.collapseStack());
         }
+
+        @Override
+        public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
+            mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
+        }
     }
 
     private class BubblesImpl implements Bubbles {
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 896a334..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
@@ -103,7 +103,8 @@
      * 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.
      */
-    private fun filterForActiveUsersAndPersist(
+    @VisibleForTesting
+    fun filterForActiveUsersAndPersist(
             activeUsers: List<Int>,
             entitiesByUser: SparseArray<List<BubbleEntity>>
     ): SparseArray<List<BubbleEntity>> {
@@ -167,7 +168,8 @@
      * 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
@@ -188,7 +190,6 @@
      *           bubbles.
      */
     @SuppressLint("WrongConstant")
-    @VisibleForTesting
     fun loadBubbles(
             userId: Int,
             currentUsers: List<Int>,
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 282db9e..f58b121 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
@@ -108,8 +108,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;
 
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 351319f..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
@@ -31,8 +31,12 @@
 
     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 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/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index b3602b3..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
@@ -215,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");
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 8ead18b..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
@@ -150,6 +150,12 @@
             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;
             boolean isOverflowExpanded = b.getKey().equals(BubbleOverflow.KEY);
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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index f8d7b6b..b15fd91 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
@@ -150,20 +150,24 @@
      * back to front during the launch.
      */
     fun stashDesktopApps(displayId: Int) {
-        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
-        desktopModeTaskRepository.setStashed(displayId, true)
+        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) {
-        KtProtoLog.v(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: hideStashedApps displayId=%d",
-                displayId
-        )
-        desktopModeTaskRepository.setStashed(displayId, false)
+        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 */
@@ -172,9 +176,13 @@
     }
 
     /** Move a task with given `taskId` to desktop */
-    fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) {
+    fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
+            taskId: Int,
+            wct: WindowContainerTransaction = WindowContainerTransaction()
+    ) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
-            task -> moveToDesktop(task, wct)
+            task -> moveToDesktop(decor, task, wct)
         }
     }
 
@@ -182,6 +190,7 @@
      * Move a task to desktop
      */
     fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
             task: RunningTaskInfo,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
@@ -195,7 +204,7 @@
         addMoveToDesktopChanges(wct, task)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+            enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -730,7 +739,7 @@
      *
      * @param taskInfo the task being dragged.
      * @param position position of surface when drag ends.
-     * @param y the Y position of the motion event.
+     * @param y the Y position of the top edge of the task
      * @param windowDecor the window decoration for the task being dragged
      */
     fun onDragPositioningEnd(
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 22929c76..16b2393 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
@@ -20,6 +20,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.graphics.PointF;
@@ -36,6 +37,7 @@
 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;
@@ -60,6 +62,7 @@
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
     private MoveToDesktopAnimator mMoveToDesktopAnimator;
+    private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -128,6 +131,18 @@
                 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,
@@ -167,138 +182,209 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+        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);
-
-            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, null));
-
-            return true;
+            return animateMoveToDesktop(change, startT, finishCallback);
         }
 
-        Rect endBounds = change.getEndAbsBounds();
+        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();
-
-            // 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, null));
-                }
-            });
-
-            animator.start();
-            return true;
+            return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
+                    endBounds);
         }
 
         if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            // 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, null));
-                }
-            });
-            animator.start();
-            return true;
+            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, 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, 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, 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, null));
+            }
+        });
+        animator.start();
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
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 5b9e47f..7d82dc17 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
@@ -352,6 +352,10 @@
                             mMainExecutor.executeDelayed(
                                     mMovePipInResponseToKeepClearAreasChangeCallback,
                                     PIP_KEEP_CLEAR_AREAS_DELAY);
+
+                            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                                    "onKeepClearAreasChanged: restricted=%s, unrestricted=%s",
+                                    restricted, unrestricted);
                         }
                     }
                 }
@@ -950,6 +954,8 @@
     }
 
     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,
@@ -1007,9 +1013,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);
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 e45dacf..e2dce88 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
@@ -168,6 +168,9 @@
     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;
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 2d7e6a6..4cc755b 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
@@ -222,7 +222,8 @@
                 && (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_DESKTOP_MODE_TOGGLE_RESIZE
+                || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
                     .addTransitionPausingRelayout(transition);
         }
@@ -356,7 +357,8 @@
                     // App sometimes draws before the insets from WindowDecoration#relayout have
                     // been added, so they must be added here
                     mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
+                    decoration.incrementRelayoutBlock();
+                    mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
                 }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
@@ -429,10 +431,10 @@
                         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;
@@ -458,10 +460,10 @@
                     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, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
+                            position, newTaskBounds.top, mWindowDecorByTaskId.get(mTaskId)));
                     mIsDragging = false;
                     return true;
                 }
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 941617d..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,6 +17,7 @@
 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.
@@ -46,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/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index e1b6db5..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
@@ -85,7 +85,7 @@
     }
 
     @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,
@@ -106,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) {
@@ -136,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/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index ae3b5eb..fb05c69 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
@@ -105,7 +105,7 @@
     }
 
     @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,
@@ -117,10 +117,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) {
         PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
                 mRepositionStartPoint);
         if (isResizing()) {
@@ -151,6 +152,7 @@
         mCtrlType = CTRL_TYPE_UNDEFINED;
         mTaskBoundsAtDragStart.setEmpty();
         mRepositionStartPoint.set(0, 0);
+        return new Rect(mRepositionTaskBounds);
     }
 
     private boolean isResizing() {
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 0947723..dfbadae 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -51,14 +51,18 @@
 }
 
 filegroup {
-    name: "WMShellFlickerTestsSplitScreenEnter-src",
+    name: "WMShellFlickerTestsSplitScreenGroup1-src",
     srcs: [
-        "src/com/android/wm/shell/flicker/splitscreen/Enter*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/A*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/B*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/C*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/D*.kt",
+        "src/com/android/wm/shell/flicker/splitscreen/E*.kt",
     ],
 }
 
 filegroup {
-    name: "WMShellFlickerTestsSplitScreenOther-src",
+    name: "WMShellFlickerTestsSplitScreenGroup2-src",
     srcs: [
         "src/com/android/wm/shell/flicker/splitscreen/*.kt",
     ],
@@ -135,8 +139,8 @@
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
         ":WMShellFlickerTestsPip-src",
-        ":WMShellFlickerTestsSplitScreenEnter-src",
-        ":WMShellFlickerTestsSplitScreenOther-src",
+        ":WMShellFlickerTestsSplitScreenGroup1-src",
+        ":WMShellFlickerTestsSplitScreenGroup2-src",
         ":WMShellFlickerTestsSplitScreenBase-src",
         ":WMShellFlickerServiceTests-src",
     ],
@@ -167,7 +171,7 @@
 }
 
 android_test {
-    name: "WMShellFlickerTestsSplitScreenEnter",
+    name: "WMShellFlickerTestsSplitScreenGroup1",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
     package_name: "com.android.wm.shell.flicker.splitscreen",
@@ -175,12 +179,12 @@
     srcs: [
         ":WMShellFlickerTestsBase-src",
         ":WMShellFlickerTestsSplitScreenBase-src",
-        ":WMShellFlickerTestsSplitScreenEnter-src",
+        ":WMShellFlickerTestsSplitScreenGroup1-src",
     ],
 }
 
 android_test {
-    name: "WMShellFlickerTestsSplitScreenOther",
+    name: "WMShellFlickerTestsSplitScreenGroup2",
     defaults: ["WMShellFlickerTestsDefault"],
     additional_manifests: ["manifests/AndroidManifestSplitScreen.xml"],
     package_name: "com.android.wm.shell.flicker.splitscreen",
@@ -188,10 +192,10 @@
     srcs: [
         ":WMShellFlickerTestsBase-src",
         ":WMShellFlickerTestsSplitScreenBase-src",
-        ":WMShellFlickerTestsSplitScreenOther-src",
+        ":WMShellFlickerTestsSplitScreenGroup2-src",
     ],
     exclude_srcs: [
-        ":WMShellFlickerTestsSplitScreenEnter-src",
+        ":WMShellFlickerTestsSplitScreenGroup1-src",
     ],
 }
 
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 13aa758..5efa51b 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
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
+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
@@ -63,10 +63,12 @@
     }
 
     /** Checks that the visible region of [pipApp] window always moves down during the animation. */
-    @FlakyTest(bugId = 292813143) @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+    @Presubmit
+    @Test
+    fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
 
     /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
-    @FlakyTest(bugId = 292813143)
+    @Presubmit
     @Test
     fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
 }
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 851391d..10dceba 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
@@ -16,9 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
+import android.tools.common.flicker.subject.layers.LayersTraceSubject
 import android.tools.common.flicker.subject.region.RegionSubject
+import android.tools.common.traces.component.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,6 +61,21 @@
         }
 
     @Test
+    @FlakyTest(bugId = 293578017)
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    // TODO(b/293578017) remove once that bug is resolve
+    @Test
+    @Presubmit
+    fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() =
+        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry(
+            LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(
+                WALLPAPER_BBQ_WRAPPER
+            )
+        ) }
+
+    @Test
     fun splitScreenDividerIsVisibleAtEnd() {
         flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
     }
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
index e5c124c..f1cb37e 100644
--- 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
@@ -265,6 +265,7 @@
         val dividerRegion =
             layer(SPLIT_SCREEN_DIVIDER_COMPONENT)?.visibleRegion?.region
                 ?: error("$SPLIT_SCREEN_DIVIDER_COMPONENT component not found")
+        visibleRegion(component).isNotEmpty()
         visibleRegion(component)
             .coversAtMost(
                 if (displayBounds.width > displayBounds.height) {
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
index 6d9d62d..0e05e01 100644
--- 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
@@ -18,18 +18,22 @@
 
 import android.app.ActivityTaskManager
 import android.content.pm.LauncherApps
-import android.content.pm.ShortcutInfo
+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.ShellExecutor
+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.Mockito
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
 
 class BubbleDataRepositoryTest : ShellTestCase() {
 
@@ -118,7 +122,8 @@
         )
     )
 
-    private val mainExecutor = mock(ShellExecutor::class.java)
+    private val testHandler = Handler(Looper.getMainLooper())
+    private val mainExecutor = HandlerExecutor(testHandler)
     private val launcherApps = mock(LauncherApps::class.java)
 
     private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -128,13 +133,11 @@
 
     @Before
     fun setup() {
-        persistentRepository = spy(BubblePersistentRepository(mContext))
-        dataRepository = BubbleDataRepository(launcherApps, mainExecutor, persistentRepository)
+        persistentRepository = BubblePersistentRepository(mContext)
+        dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
 
-        // Add the bubbles to the persistent repository
         persistedBubbles.put(0, user0BubbleEntities)
         persistedBubbles.put(1, user1BubbleEntities)
-        persistentRepository.persistsToDisk(persistedBubbles)
     }
 
     @After
@@ -144,47 +147,58 @@
     }
 
     @Test
-    fun testLoadBubbles_invalidParent() {
-        val activeUserIds = listOf(10, 1, 12) // Missing user 0 in persistedBubbles
-        dataRepository.loadBubbles(1, activeUserIds) {
-            // Verify that user 0 has been removed from the persisted list
-            val entitiesByUser = persistentRepository.readFromDisk()
-            assertThat(entitiesByUser.get(0)).isNull()
-        }
+    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(SparseArray<List<BubbleEntity>>()::class.java))
     }
 
     @Test
-    fun testLoadBubbles_invalidChild() {
+    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
-        dataRepository.loadBubbles(1, activeUserIds) {
-            // Build a list to compare against
-            val user1BubblesWithoutUser12 = mutableListOf<Bubble>()
-            val user1EntitiesWithoutUser12 = mutableListOf<BubbleEntity>()
-            for (entity in user1BubbleEntities) {
-                if (entity.userId != 12) {
-                    user1BubblesWithoutUser12.add(entity.toBubble())
-                    user1EntitiesWithoutUser12.add(entity)
-                }
-            }
+        val validEntitiesByUser = dataRepository.filterForActiveUsersAndPersist(
+            activeUserIds, persistedBubbles)
 
-            // Verify that user 12 has been removed from the persisted list
-            val entitiesByUser = persistentRepository.readFromDisk()
-            assertThat(entitiesByUser.get(1)).isEqualTo(user1EntitiesWithoutUser12)
-        }
+        // 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)
     }
 
-    private fun BubbleEntity.toBubble(): Bubble {
-        return Bubble(
-            key,
-            mock(ShortcutInfo::class.java),
-            desiredHeight,
-            desiredHeightResId,
-            title,
-            taskId,
-            locus,
-            isDismissable,
-            mainExecutor,
-            mock(Bubbles.BubbleMetadataFlagListener::class.java)
-        )
-    }
+    fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
 }
\ No newline at end of file
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 1477cf7..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
@@ -57,6 +57,7 @@
 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
@@ -92,6 +93,7 @@
     @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
@@ -276,8 +278,8 @@
     fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
-        controller.moveToDesktop(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
@@ -286,15 +288,15 @@
     fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
-        controller.moveToDesktop(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        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()
     }
 
@@ -305,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)
@@ -327,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())
@@ -498,6 +500,7 @@
     @Test
     fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
 
         val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
         markTaskHidden(stashedFreeformTask)
@@ -569,6 +572,7 @@
     @Test
     fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
 
         val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
         markTaskHidden(stashedFreeformTask)
@@ -626,6 +630,8 @@
 
     @Test
     fun stashDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
         controller.stashDesktopApps(DEFAULT_DISPLAY)
 
         assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
@@ -634,6 +640,8 @@
 
     @Test
     fun hideStashedDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
         desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
         desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
         controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
@@ -715,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/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f27a8de..6b67d14 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -460,6 +460,9 @@
      * updates} in order to keep the system UI in a consistent state. You can also call this method
      * at any other point to update the listing preference dynamically.
      *
+     * <p>Any calls to this method from a privileged router will throw an {@link
+     * UnsupportedOperationException}.
+     *
      * <p>Notes:
      *
      * <ol>
@@ -476,24 +479,7 @@
      *     route listing. When null, the system uses its default listing criteria.
      */
     public void setRouteListingPreference(@Nullable RouteListingPreference routeListingPreference) {
-        synchronized (mLock) {
-            if (Objects.equals(mRouteListingPreference, routeListingPreference)) {
-                // Nothing changed. We return early to save a call to the system server.
-                return;
-            }
-            mRouteListingPreference = routeListingPreference;
-            try {
-                if (mStub == null) {
-                    MediaRouter2Stub stub = new MediaRouter2Stub();
-                    mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
-                    mStub = stub;
-                }
-                mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
-            } catch (RemoteException ex) {
-                ex.rethrowFromSystemServer();
-            }
-            notifyRouteListingPreferenceUpdated(routeListingPreference);
-        }
+        mImpl.setRouteListingPreference(routeListingPreference);
     }
 
     /**
@@ -1962,6 +1948,8 @@
 
         void unregisterRouteCallback();
 
+        void setRouteListingPreference(@Nullable RouteListingPreference preference);
+
         List<MediaRoute2Info> getAllRoutes();
 
         void setOnGetControllerHintsListener(OnGetControllerHintsListener listener);
@@ -2102,6 +2090,12 @@
             // Do nothing.
         }
 
+        @Override
+        public void setRouteListingPreference(@Nullable RouteListingPreference preference) {
+            throw new UnsupportedOperationException(
+                    "RouteListingPreference cannot be set by a privileged MediaRouter2 instance.");
+        }
+
         /** Gets the list of all discovered routes. */
         @Override
         public List<MediaRoute2Info> getAllRoutes() {
@@ -2892,6 +2886,28 @@
             }
         }
 
+        @Override
+        public void setRouteListingPreference(@Nullable RouteListingPreference preference) {
+            synchronized (mLock) {
+                if (Objects.equals(mRouteListingPreference, preference)) {
+                    // Nothing changed. We return early to save a call to the system server.
+                    return;
+                }
+                mRouteListingPreference = preference;
+                try {
+                    if (mStub == null) {
+                        MediaRouter2Stub stub = new MediaRouter2Stub();
+                        mMediaRouterService.registerRouter2(stub, mImpl.getPackageName());
+                        mStub = stub;
+                    }
+                    mMediaRouterService.setRouteListingPreference(mStub, mRouteListingPreference);
+                } catch (RemoteException ex) {
+                    ex.rethrowFromSystemServer();
+                }
+                notifyRouteListingPreferenceUpdated(preference);
+            }
+        }
+
         /**
          * Returns {@link Collections#emptyList()}. Local routes can only access routes related to
          * their {@link RouteDiscoveryPreference} through {@link #getRoutes()}.
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 0c6096e..6e2aaab 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -130,7 +130,7 @@
 
     /**
      * Returns the {@link MidiDeviceInfo} instance for this service
-     * @return the MidiDeviceInfo of the virtual MIDI device
+     * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
      */
     public final @Nullable MidiDeviceInfo getDeviceInfo() {
         return mDeviceInfo;
@@ -140,7 +140,7 @@
      * Called to notify when the {@link MidiDeviceStatus} has changed
      * @param status the current status of the MIDI device
      */
-    public void onDeviceStatusChanged(@Nullable MidiDeviceStatus status) {
+    public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
     }
 
     /**
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4193ffa..ac8e4d4 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -64,6 +64,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -454,6 +455,7 @@
         assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
 
+    @Ignore // TODO(b/291800179): Diagnose flakiness and re-enable.
     @Test
     public void testRouterRelease_managerGetRoutingSessions() throws Exception {
         CountDownLatch transferLatch = new CountDownLatch(1);
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index 6a5d2c0..fd394fc 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -12,6 +12,15 @@
            }
        ],
        "file_patterns": ["permission_manager.cpp"]
+    },
+    {
+       "name": "CtsOsTestCases",
+       "options": [
+           {
+              "include-filter": "android.os.cts.PerformanceHintManagerTest"
+           }
+       ],
+       "file_patterns": ["performance_hint.cpp"]
     }
   ]
 }
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index b3628fa..6198f40 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -274,9 +274,10 @@
     binder::Status ret = mHintManager->setHintSessionThreads(mHintSession, tids);
     if (!ret.isOk()) {
         ALOGE("%s: failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
-        if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY ||
-            ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
+        if (ret.exceptionCode() == binder::Status::Exception::EX_ILLEGAL_ARGUMENT) {
             return EINVAL;
+        } else if (ret.exceptionCode() == binder::Status::Exception::EX_SECURITY) {
+            return EPERM;
         }
         return EPIPE;
     }
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 791adfd..6f7562b 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -178,4 +178,15 @@
             .WillOnce(Return(Status()));
     result = APerformanceHint_setThreads(session, newTids.data(), newTids.size());
     EXPECT_EQ(0, result);
+
+    testing::Mock::VerifyAndClearExpectations(mMockIHintManager);
+    std::vector<int32_t> invalidTids;
+    auto status = Status::fromExceptionCode(binder::Status::Exception::EX_SECURITY);
+    invalidTids.push_back(4);
+    invalidTids.push_back(6);
+    EXPECT_CALL(*mMockIHintManager, setHintSessionThreads(_, Eq(invalidTids)))
+            .Times(Exactly(1))
+            .WillOnce(Return(status));
+    result = APerformanceHint_setThreads(session, invalidTids.data(), invalidTids.size());
+    EXPECT_EQ(EPERM, result);
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
index 8639f47..0d1475a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
@@ -16,6 +16,8 @@
 
 package com.android.packageinstaller;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
@@ -186,7 +188,9 @@
         int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
 
         if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
-            context.startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
+            Intent intentToStart = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            intentToStart.addFlags(FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(intentToStart);
 
             return;
         }
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c244ca0..3d35bad 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -58,6 +58,7 @@
         "setupdesign",
         "zxing-core-1.7",
         "androidx.room_room-runtime",
+        "settingslib_flags_lib",
 
     ],
 
@@ -88,3 +89,16 @@
         "SettingsLib",
     ],
 }
+
+aconfig_declarations {
+    name: "settingslib_media_flags",
+    package: "com.android.settingslib.media.flags",
+    srcs: [
+        "aconfig/settingslib_media_flag_declarations.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "settingslib_flags_lib",
+    aconfig_declarations: "settingslib_media_flags",
+}
diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml
index 13f8a37..322d6cf 100644
--- a/packages/SettingsLib/AndroidManifest.xml
+++ b/packages/SettingsLib/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.settingslib">
 
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+
     <application>
         <activity
             android:name="com.android.settingslib.users.AvatarPickerActivity"
diff --git a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
index 5d6c343..accaa67 100644
--- a/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
+++ b/packages/SettingsLib/IllustrationPreference/res/values/colors.xml
@@ -25,10 +25,12 @@
     <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>
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index 5e2c437..f166a18 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -42,9 +42,6 @@
                 ".grey600",
                 R.color.settingslib_color_grey400);
         map.put(
-                ".grey700",
-                R.color.settingslib_color_grey500);
-        map.put(
                 ".grey800",
                 R.color.settingslib_color_grey300);
         map.put(
@@ -62,6 +59,12 @@
         map.put(
                 ".green400",
                 R.color.settingslib_color_green600);
+        map.put(
+                ".green200",
+                R.color.settingslib_color_green500);
+        map.put(
+                ".red200",
+                R.color.settingslib_color_red500);
         DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
     }
 
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 864a8bb..c8df760 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -123,8 +123,7 @@
 
     @Override
     public boolean performClick() {
-        mSwitch.performClick();
-        return super.performClick();
+        return mSwitch.performClick();
     }
 
     /**
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9a16df8..ee40b02 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.0.2"
+agp = "8.1.0"
 dexmaker-mockito = "2.28.3"
 kotlin = "1.8.10"
 truth = "1.1"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
index e708b1c..c1962a7 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 33f49e3..5b0ac44 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -15,7 +15,7 @@
 #
 
 distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
 distributionPath=wrapper/dists
-zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
 zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/gradlew b/packages/SettingsLib/Spa/gradlew
index 4f906e0..aeb74cb 100755
--- a/packages/SettingsLib/Spa/gradlew
+++ b/packages/SettingsLib/Spa/gradlew
@@ -1,7 +1,7 @@
-#!/usr/bin/env sh
+#!/bin/sh
 
 #
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,67 +17,98 @@
 #
 
 ##############################################################################
-##
-##  Gradle start up script for UN*X
-##
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
 ##############################################################################
 
 # Attempt to set APP_HOME
+
 # Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
-    ls=`ls -ld "$PRG"`
-    link=`expr "$ls" : '.*-> \(.*\)$'`
-    if expr "$link" : '/.*' > /dev/null; then
-        PRG="$link"
-    else
-        PRG=`dirname "$PRG"`"/$link"
-    fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
 done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
 
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
 
 warn () {
     echo "$*"
-}
+} >&2
 
 die () {
     echo
     echo "$*"
     echo
     exit 1
-}
+} >&2
 
 # OS specific support (must be 'true' or 'false').
 cygwin=false
 msys=false
 darwin=false
 nonstop=false
-case "`uname`" in
-  CYGWIN* )
-    cygwin=true
-    ;;
-  Darwin* )
-    darwin=true
-    ;;
-  MINGW* )
-    msys=true
-    ;;
-  NONSTOP* )
-    nonstop=true
-    ;;
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
 esac
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +118,9 @@
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
         # IBM's JDK on AIX uses strange locations for the executables
-        JAVACMD="$JAVA_HOME/jre/sh/java"
+        JAVACMD=$JAVA_HOME/jre/sh/java
     else
-        JAVACMD="$JAVA_HOME/bin/java"
+        JAVACMD=$JAVA_HOME/bin/java
     fi
     if [ ! -x "$JAVACMD" ] ; then
         die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +129,7 @@
 location of your Java installation."
     fi
 else
-    JAVACMD="java"
+    JAVACMD=java
     which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +137,109 @@
 fi
 
 # Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
-    MAX_FD_LIMIT=`ulimit -H -n`
-    if [ $? -eq 0 ] ; then
-        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
-            MAX_FD="$MAX_FD_LIMIT"
-        fi
-        ulimit -n $MAX_FD
-        if [ $? -ne 0 ] ; then
-            warn "Could not set maximum file descriptor limit: $MAX_FD"
-        fi
-    else
-        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
-    fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
-    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
-    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
-    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
-    JAVACMD=`cygpath --unix "$JAVACMD"`
-
-    # We build the pattern for arguments to be converted via cygpath
-    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
-    SEP=""
-    for dir in $ROOTDIRSRAW ; do
-        ROOTDIRS="$ROOTDIRS$SEP$dir"
-        SEP="|"
-    done
-    OURCYGPATTERN="(^($ROOTDIRS))"
-    # Add a user-defined pattern to the cygpath arguments
-    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
-        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
-    fi
-    # Now convert the arguments - kludge to limit ourselves to /bin/sh
-    i=0
-    for arg in "$@" ; do
-        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
-        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
-
-        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
-            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
-        else
-            eval `echo args$i`="\"$arg\""
-        fi
-        i=`expr $i + 1`
-    done
-    case $i in
-        0) set -- ;;
-        1) set -- "$args0" ;;
-        2) set -- "$args0" "$args1" ;;
-        3) set -- "$args0" "$args1" "$args2" ;;
-        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+        # shellcheck disable=SC3045
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
 fi
 
-# Escape application args
-save () {
-    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
-    echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
 
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
 
 exec "$JAVACMD" "$@"
diff --git a/packages/SettingsLib/Spa/gradlew.bat b/packages/SettingsLib/Spa/gradlew.bat
deleted file mode 100644
index 107acd32..0000000
--- a/packages/SettingsLib/Spa/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem      https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem  Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 79f8c46..7f5948c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaLib",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "androidx.slice_slice-builders",
         "androidx.slice_slice-core",
@@ -50,5 +50,6 @@
 // Expose the srcs to tests, so the tests can access the internal classes.
 filegroup {
     name: "SpaLib_srcs",
+    visibility: ["//frameworks/base/packages/SettingsLib/Spa/tests"],
     srcs: ["src/**/*.kt"],
 }
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 188e7f6..377e72ed 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,11 +52,11 @@
 }
 
 dependencies {
-    api("androidx.appcompat:appcompat:1.7.0-alpha02")
+    api("androidx.appcompat:appcompat:1.7.0-alpha03")
     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.material3:material3:1.2.0-alpha04")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index e4d56cc..65f5d34 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaLibTestUtils",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "SpaLib",
         "androidx.arch.core_core-testing",
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 4a7418f..eaeda3c 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaPrivilegedLib",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "SpaLib",
         "SettingsLib",
@@ -45,5 +45,6 @@
 // Expose the srcs to tests, so the tests can access the internal classes.
 filegroup {
     name: "SpaPrivilegedLib_srcs",
+    visibility: [":__subpackages__"],
     srcs: ["src/**/*.kt"],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt
new file mode 100644
index 0000000..05cb1b1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.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.settingslib.spaprivileged.framework.compose
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.R
+
+/** An empty placer holder string. */
+@Composable
+fun placeholder() = stringResource(R.string.summary_placeholder)
+
+/** Gets an empty placer holder string. */
+fun Context.getPlaceholder(): String = getString(R.string.summary_placeholder)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 1a7d896..de2cf1f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -23,11 +23,11 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -40,7 +40,7 @@
     @Composable
     fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
         val context = LocalContext.current
-        return produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
+        return produceState(initialValue = placeholder(), app) {
             withContext(Dispatchers.IO) {
                 value = if (isClonedAppPage || isCloneApp(context, app)) {
                     context.getString(R.string.cloned_app_info_label, loadLabel(app))
@@ -82,7 +82,7 @@
             withContext(Dispatchers.IO) {
                 value = when {
                     context.userManager.isManagedProfile(app.userId) -> {
-                        context.getString(R.string.category_work)
+                        context.getString(com.android.settingslib.R.string.category_work)
                     }
 
                     else -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index fab3ae8..cc3584b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -20,7 +20,7 @@
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
 import android.content.Context
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.R
 
 class EnterpriseRepository(private val context: Context) {
     private val resources by lazy {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index ae362c8..e2ff7b0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -27,7 +27,7 @@
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.settingslib.RestrictedLockUtilsInternal
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.widget.R
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
@@ -56,11 +56,15 @@
 
     override fun getSummary(checked: Boolean?) = when (checked) {
         true -> enterpriseRepository.getEnterpriseString(
-            Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
+            updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.enabled_by_admin,
         )
+
         false -> enterpriseRepository.getEnterpriseString(
-            Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin
+            updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.disabled_by_admin,
         )
+
         else -> ""
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index b43210f..cee750e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -72,7 +72,7 @@
     private fun InstallType(app: ApplicationInfo) {
         if (!app.isInstantApp) return
         Spacer(modifier = Modifier.height(4.dp))
-        SettingsBody(stringResource(R.string.install_type_instant))
+        SettingsBody(stringResource(com.android.settingslib.widget.R.string.install_type_instant))
     }
 
     @Composable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 3e96994..5fc1972 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -24,9 +24,8 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
@@ -36,7 +35,7 @@
 @Composable
 fun ApplicationInfo.getStorageSize(): State<String> {
     val context = LocalContext.current
-    return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+    return produceState(initialValue = placeholder()) {
         withContext(Dispatchers.IO) {
             val sizeBytes = calculateSizeBytes(context)
             value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index cbc4822..1fa854a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -38,6 +38,7 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
@@ -173,7 +174,7 @@
         when (allowed.value) {
             true -> context.getString(R.string.app_permission_summary_allowed)
             false -> context.getString(R.string.app_permission_summary_not_allowed)
-            null -> context.getString(R.string.summary_placeholder)
+            null -> context.getPlaceholder()
         }
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index b08b6df..e77dcd4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -32,7 +32,7 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
@@ -72,9 +72,12 @@
         checked: State<Boolean?>,
     ): State<String> = when (restrictedMode) {
         is NoRestricted -> summaryIfNoRestricted
-        is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
+        is BaseUserRestricted -> stateOf(
+            context.getString(com.android.settingslib.R.string.disabled)
+        )
+
         is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
-        null -> stateOf(context.getString(R.string.summary_placeholder))
+        null -> stateOf(context.getPlaceholder())
     }
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
index 26caa01..d11e63a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.delay
-import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -66,7 +65,8 @@
 
         val contentDescription = produceIconContentDescription()
 
-        assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work))
+        assertThat(contentDescription.value)
+            .isEqualTo(context.getString(com.android.settingslib.R.string.category_work))
     }
 
     @Test
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index f6f4889..82fbee9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -116,7 +116,7 @@
 
     private fun onMoreOptions() =
         composeTestRule.onNodeWithContentDescription(
-            context.getString(R.string.abc_action_menu_overflow_description)
+            context.getString(androidx.appcompat.R.string.abc_action_menu_overflow_description)
         )
 
     private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 961ec10..457b810 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
 import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
@@ -95,9 +96,7 @@
 
         val summaryState = getSummary(listModel)
 
-        assertThat(summaryState.value).isEqualTo(
-            context.getString(R.string.summary_placeholder)
-        )
+        assertThat(summaryState.value).isEqualTo(context.getPlaceholder())
     }
 
     @Test
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
new file mode 100644
index 0000000..0b74fa8
--- /dev/null
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.settingslib.media.flags"
+
+flag {
+  name: "use_media_router2_for_info_media_manager"
+  namespace: "placeholder_namespace"
+  description: "Gates whether to use a MediaRouter2-based implementation of InfoMediaManager, instead of the legacy MediaRouter2Manager-based implementation."
+  bug: "192657812"
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 7e27560..bf63f5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -62,6 +62,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.media.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -82,6 +83,13 @@
     private static final String TAG = "InfoMediaManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    /** Checked exception that signals the specified package is not present in the system. */
+    public static class PackageNotAvailableException extends Exception {
+        public PackageNotAvailableException(String message) {
+            super(message);
+        }
+    }
+
     protected String mPackageName;
     private MediaDevice mCurrentConnectedDevice;
     private final LocalBluetoothManager mBluetoothManager;
@@ -98,6 +106,28 @@
         }
     }
 
+    /** Creates an instance of InfoMediaManager. */
+    public static InfoMediaManager createInstance(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
+        if (Flags.useMediaRouter2ForInfoMediaManager()) {
+            try {
+                return new RouterInfoMediaManager(
+                        context, packageName, notification, localBluetoothManager);
+            } catch (PackageNotAvailableException ex) {
+                // TODO: b/293578081 - Propagate this exception to callers for proper handling.
+                Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
+                return new NoOpInfoMediaManager(
+                        context, packageName, notification, localBluetoothManager);
+            }
+        } else {
+            return new ManagerInfoMediaManager(
+                    context, packageName, notification, localBluetoothManager);
+        }
+    }
+
     @Override
     public void startScan() {
         mMediaDevices.clear();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 4fb0487..fe3ef5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -137,7 +137,7 @@
         }
 
         mInfoMediaManager =
-                new ManagerInfoMediaManager(
+                InfoMediaManager.createInstance(
                         context, packageName, notification, mLocalBluetoothManager);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
new file mode 100644
index 0000000..9d578bc
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.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.settingslib.media;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * No-op implementation of {@link InfoMediaManager}.
+ *
+ * <p>This implementation is used when {@link RouterInfoMediaManager} throws a {@link
+ * InfoMediaManager.PackageNotAvailableException}.
+ */
+// TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
+/* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+
+    NoOpInfoMediaManager(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) {
+        super(context, packageName, notification, localBluetoothManager);
+    }
+
+    @Override
+    public void stopScan() {
+        // Do nothing.
+    }
+
+    @Override
+    protected void startScanOnRouter() {
+        // Do nothing.
+    }
+
+    @Override
+    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+        return false;
+    }
+
+    @Override
+    protected void transferToRoute(@NonNull MediaRoute2Info route) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+        // Do nothing.
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+        return Collections.emptyList();
+    }
+
+    @Override
+    protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+        // Do nothing.
+    }
+
+    @Override
+    protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+        // Do nothing.
+    }
+
+    @Nullable
+    @Override
+    protected RouteListingPreference getRouteListingPreference() {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRemoteSessions() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+        return Collections.emptyList();
+    }
+
+    @Nullable
+    @Override
+    protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+        return null;
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAllRoutes() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+        return Collections.emptyList();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+        return Collections.emptyList();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
new file mode 100644
index 0000000..70956e9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
+@SuppressLint("MissingPermission")
+public final class RouterInfoMediaManager extends InfoMediaManager {
+
+    private static final String TAG = "RouterInfoMediaManager";
+
+    private final MediaRouter2 mRouter;
+    private final MediaRouter2Manager mRouterManager;
+
+    private final Executor mExecutor = Executors.newSingleThreadExecutor();
+
+    private final RouteCallback mRouteCallback = new RouteCallback();
+    private final TransferCallback mTransferCallback = new TransferCallback();
+    private final ControllerCallback mControllerCallback = new ControllerCallback();
+    private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
+            new RouteListingPreferenceCallback();
+
+    // TODO: b/192657812 - Create factory method in InfoMediaManager to return
+    //      RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+    public RouterInfoMediaManager(
+            Context context,
+            String packageName,
+            Notification notification,
+            LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+        super(context, packageName, notification, localBluetoothManager);
+
+        // TODO: b/291277292 - Change optional package name for a mandatory uid.
+        if (packageName == null) {
+            packageName = context.getPackageName();
+        }
+
+        mRouter = MediaRouter2.getInstance(context, packageName);
+
+        if (mRouter == null) {
+            throw new PackageNotAvailableException(
+                    "Package name " + packageName + " does not exist.");
+        }
+        mRouterManager = MediaRouter2Manager.getInstance(context);
+    }
+
+    @Override
+    protected void startScanOnRouter() {
+        mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
+        mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+        mRouter.registerTransferCallback(mExecutor, mTransferCallback);
+        mRouter.registerControllerCallback(mExecutor, mControllerCallback);
+        mRouter.startScan();
+    }
+
+    @Override
+    public void stopScan() {
+        mRouter.stopScan();
+        mRouter.unregisterControllerCallback(mControllerCallback);
+        mRouter.unregisterTransferCallback(mTransferCallback);
+        mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+        mRouter.unregisterRouteCallback(mRouteCallback);
+    }
+
+    @Override
+    protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+        if (device.mRouteInfo == null) {
+            return false;
+        }
+
+        RoutingController controller = mRouter.getSystemController();
+        mRouter.transfer(controller, device.mRouteInfo);
+        return true;
+    }
+
+    @Override
+    protected void transferToRoute(@NonNull MediaRoute2Info route) {
+        mRouter.transferTo(route);
+    }
+
+    @Override
+    protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        RoutingController controller = getControllerForSession(info);
+        if (controller != null) {
+            controller.selectRoute(route);
+        }
+    }
+
+    @Override
+    protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+        RoutingController controller = getControllerForSession(info);
+        if (controller != null) {
+            controller.deselectRoute(route);
+        }
+    }
+
+    @Override
+    protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+        RoutingController controller = getControllerForSession(sessionInfo);
+        if (controller != null) {
+            controller.release();
+        }
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+        RoutingController controller = getControllerForSession(info);
+        if (controller == null) {
+            return Collections.emptyList();
+        }
+
+        // Filter out selected routes.
+        List<String> selectedRouteIds = controller.getRoutingSessionInfo().getSelectedRoutes();
+        return controller.getSelectableRoutes().stream()
+                .filter(route -> !selectedRouteIds.contains(route.getId()))
+                .collect(Collectors.toList());
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+        RoutingController controller = getControllerForSession(info);
+        if (controller == null) {
+            return Collections.emptyList();
+        }
+
+        return controller.getDeselectableRoutes();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+        RoutingController controller = getControllerForSession(info);
+        if (controller == null) {
+            return Collections.emptyList();
+        }
+        return controller.getSelectedRoutes();
+    }
+
+    @Override
+    protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
+        //      MR2 filters information by package name.
+        mRouterManager.setSessionVolume(info, volume);
+    }
+
+    @Override
+    protected void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
+        mRouter.setRouteVolume(route, volume);
+    }
+
+    @Nullable
+    @Override
+    protected RouteListingPreference getRouteListingPreference() {
+        return mRouter.getRouteListingPreference();
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRemoteSessions() {
+        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager call as
+        //      MR2 filters information by package name.
+        return mRouterManager.getRemoteSessions();
+    }
+
+    @NonNull
+    @Override
+    protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+        return mRouter.getControllers().stream()
+                .map(RoutingController::getRoutingSessionInfo)
+                .collect(Collectors.toList());
+    }
+
+    @Nullable
+    @Override
+    protected RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId) {
+        // TODO: b/291277292 - Implement MediaRouter2-based solution. Keeping MR2Manager calls as
+        //      MR2 filters information by package name.
+
+        for (RoutingSessionInfo sessionInfo : getRemoteSessions()) {
+            if (TextUtils.equals(sessionInfo.getId(), sessionId)) {
+                return sessionInfo;
+            }
+        }
+
+        RoutingSessionInfo systemSession = mRouterManager.getSystemRoutingSession(null);
+        return TextUtils.equals(systemSession.getId(), sessionId) ? systemSession : null;
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAllRoutes() {
+        return mRouter.getAllRoutes();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+        return mRouter.getRoutes();
+    }
+
+    @NonNull
+    @Override
+    protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+        List<MediaRoute2Info> transferableRoutes = new ArrayList<>();
+
+        List<RoutingController> controllers = mRouter.getControllers();
+        RoutingController activeController = controllers.get(controllers.size() - 1);
+        RoutingSessionInfo sessionInfo = activeController.getRoutingSessionInfo();
+        List<MediaRoute2Info> routes = mRouter.getRoutes();
+
+        for (MediaRoute2Info route : routes) {
+            boolean isCrossDeviceTransfer = sessionInfo.isSystemSession() ^ route.isSystemRoute();
+
+            // Always show remote routes if transfer is local -> remote or viceversa regardless of
+            // whether route is in transferable routes list.
+            if (sessionInfo.getTransferableRoutes().contains(route.getId())
+                    || isCrossDeviceTransfer) {
+                transferableRoutes.add(route);
+            }
+        }
+
+        return transferableRoutes;
+    }
+
+    @Nullable
+    private RoutingController getControllerForSession(@NonNull RoutingSessionInfo sessionInfo) {
+        return mRouter.getController(sessionInfo.getId());
+    }
+
+    private final class RouteCallback extends MediaRouter2.RouteCallback {
+        @Override
+        public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {
+            refreshDevices();
+        }
+
+        @Override
+        public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {
+            refreshDevices();
+        }
+    }
+
+    private final class TransferCallback extends MediaRouter2.TransferCallback {
+        @Override
+        public void onTransfer(
+                @NonNull RoutingController oldController,
+                @NonNull RoutingController newController) {
+            rebuildDeviceList();
+            notifyCurrentConnectedDeviceChanged();
+        }
+
+        @Override
+        public void onTransferFailure(@NonNull MediaRoute2Info requestedRoute) {
+            // Do nothing.
+        }
+
+        @Override
+        public void onStop(@NonNull RoutingController controller) {
+            refreshDevices();
+        }
+
+        @Override
+        public void onRequestFailed(int reason) {
+            dispatchOnRequestFailed(reason);
+        }
+    }
+
+    private final class ControllerCallback extends MediaRouter2.ControllerCallback {
+        @Override
+        public void onControllerUpdated(@NonNull RoutingController controller) {
+            refreshDevices();
+        }
+    }
+
+    private final class RouteListingPreferenceCallback
+            extends MediaRouter2.RouteListingPreferenceCallback {
+        @Override
+        public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
+            notifyRouteListingPreferenceUpdated(preference);
+            refreshDevices();
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index ff3eeec..8970deb 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -48,11 +48,14 @@
         "androidx.test.core",
         "androidx.test.rules",
         "androidx.test.espresso.core",
+        "flag-junit",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
         "truth-prebuilt",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibSettingsSpinner",
         "SettingsLibUsageProgressBarPreference",
+        "settingslib_flags_lib",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
new file mode 100644
index 0000000..c647cbb
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.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.settingslib.media;
+
+import static com.android.settingslib.media.flags.Flags.FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InfoMediaManagerIntegTest {
+
+    private static final String FAKE_PACKAGE = "FAKE_PACKAGE";
+
+    private Context mContext;
+    private UiAutomation mUiAutomation;
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
+    }
+
+    @After
+    public void tearDown() {
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+        assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+        assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
+        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+        assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
+    public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+        assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
+    }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 368115b..215cc8e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -337,6 +337,7 @@
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
     <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+    <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
 
     <uses-permission android:name="android.permission.MANAGE_APPOPS" />
     <uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 712b737..0474849 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -201,6 +201,7 @@
         "lottie",
         "LowLightDreamLib",
         "motion_tool_lib",
+        "IntentResolver-core",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -384,6 +385,7 @@
         "motion_tool_lib",
         "androidx.core_core-animation-testing-nodeps",
         "androidx.compose.ui_ui",
+        "IntentResolver-core",
     ],
 }
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index bba926d..0f1f168 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -89,7 +89,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/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1bd8355..969c148 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -56,6 +56,20 @@
       ]
     },
     {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    },
+    {
       // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
       "name": "SystemUIGoogleBiometricsScreenshotTests",
       "options": [
@@ -148,5 +162,21 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+   {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+            "include-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 5ed450a..c26cd12 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -25,8 +25,8 @@
 import android.os.Bundle;
 import android.provider.Browser;
 import android.provider.Settings;
-import android.widget.TextView;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
@@ -55,6 +55,10 @@
         ((TextView) findViewById(R.id.action_bar_title)).setText(
                 getResources().getString(R.string.accessibility_menu_settings_name)
         );
+        actionBar.setDisplayOptions(
+                ActionBar.DISPLAY_TITLE_MULTIPLE_LINES
+                        | ActionBar.DISPLAY_SHOW_TITLE
+                        | ActionBar.DISPLAY_HOME_AS_UP);
     }
 
     /**
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/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/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/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index d83596e..6d9497d 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
@@ -52,24 +52,27 @@
 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
 
 /** 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),
@@ -79,7 +82,6 @@
 
     @Composable
     override fun Content(
-        containerName: String,
         modifier: Modifier,
     ) = BouncerScene(viewModel, dialogFactory, modifier)
 }
@@ -87,7 +89,7 @@
 @Composable
 private fun BouncerScene(
     viewModel: BouncerViewModel,
-    dialogFactory: () -> AlertDialog,
+    dialogFactory: BouncerSceneDialogFactory,
     modifier: Modifier = Modifier,
 ) {
     val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState()
@@ -175,3 +177,7 @@
         }
     }
 }
+
+interface BouncerSceneDialogFactory {
+    operator fun invoke(): AlertDialog
+}
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..ab7bc26 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
@@ -31,6 +31,7 @@
 import androidx.compose.ui.unit.dp
 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 +39,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,15 +47,16 @@
 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(
@@ -64,7 +67,6 @@
 
     @Composable
     override fun Content(
-        containerName: String,
         modifier: Modifier,
     ) {
         LockscreenScene(
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..d84e676 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,6 +38,7 @@
 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
@@ -134,10 +134,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 +152,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 +161,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/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 30b80ca..130395a 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
@@ -27,25 +27,28 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
 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),
@@ -55,7 +58,6 @@
 
     @Composable
     override fun Content(
-        containerName: String,
         modifier: Modifier,
     ) {
         QuickSettingsScene(
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..a213666 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
@@ -22,5 +22,5 @@
 
 /** Compose-capable extension of [Scene]. */
 interface ComposableScene : Scene {
-    @Composable fun Content(containerName: String, modifier: Modifier)
+    @Composable fun 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..0070552 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,12 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+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 +37,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),
@@ -50,7 +51,6 @@
 
     @Composable
     override fun Content(
-        containerName: String,
         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 3298664..49e2bf9 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
@@ -75,7 +75,6 @@
             if (key == currentSceneKey) {
                 Scene(
                     scene = composableScene,
-                    containerName = viewModel.containerName,
                     onSceneChanged = viewModel::setCurrentScene,
                     modifier = Modifier.fillMaxSize(),
                 )
@@ -88,12 +87,10 @@
 @Composable
 private fun Scene(
     scene: ComposableScene,
-    containerName: String,
     onSceneChanged: (SceneModel) -> Unit,
     modifier: Modifier = Modifier,
 ) {
-    val destinationScenes: Map<UserAction, SceneModel> by
-        scene.destinationScenes(containerName).collectAsState()
+    val destinationScenes: Map<UserAction, SceneModel> by scene.destinationScenes().collectAsState()
     val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
     val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
     val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
@@ -107,7 +104,6 @@
             modifier = Modifier.align(Alignment.Center),
         ) {
             scene.Content(
-                containerName = containerName,
                 modifier = Modifier,
             )
 
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 27358f5..b73e0b2 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
@@ -26,6 +26,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+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
@@ -35,6 +36,7 @@
 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
@@ -42,15 +44,16 @@
 import kotlinx.coroutines.flow.stateIn
 
 /** 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(
@@ -61,7 +64,6 @@
 
     @Composable
     override fun Content(
-        containerName: String,
         modifier: Modifier,
     ) = ShadeScene(viewModel, modifier)
 
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 d43276c..46f5971 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
@@ -190,6 +190,10 @@
         /** Flag denoting transit clock are enabled in wallpaper picker. */
         const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
 
+        /** 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/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index c7d2d81..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.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_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/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index e474938..5404cfa 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.internal.widget.ResolverDrawerLayout
+<com.android.intentresolver.widget.ResolverDrawerLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
@@ -84,7 +84,7 @@
                 android:id="@*android:id/tabcontent"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
-                <com.android.internal.app.ResolverViewPager
+                <com.android.intentresolver.ResolverViewPager
                     android:id="@*android:id/profile_pager"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"/>
@@ -92,4 +92,4 @@
         </LinearLayout>
     </TabHost>
 
-</com.android.internal.widget.ResolverDrawerLayout>
+</com.android.intentresolver.widget.ResolverDrawerLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de8287e..68fcdea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1196,7 +1196,6 @@
     <dimen name="magnification_window_drag_corner_stroke">3dp</dimen>
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
-    <dimen name="magnification_max_frame_size">300dp</dimen>
     <!-- Magnification settings panel -->
     <dimen name="magnification_setting_view_margin">24dp</dimen>
     <dimen name="magnification_setting_text_size">18sp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 4d3a742..2c224f62 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -423,6 +423,7 @@
      * Refresh clock. Called in response to TIME_TICK broadcasts.
      */
     void refresh() {
+        mLogBuffer.log(TAG, LogLevel.INFO, "refresh");
         if (mSmartspaceController != null) {
             mSmartspaceController.requestSmartspaceUpdate();
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 61addab..8a0c9ef 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -1198,6 +1198,8 @@
                 });
                 mPopup.show();
             });
+
+            mUserSwitcherViewGroup.setAlpha(0f);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index bc24249..3b09910f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -81,7 +81,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneContainerNames;
 import com.android.systemui.scene.shared.model.SceneKey;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -476,7 +475,7 @@
         if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
             // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mSceneInteractor.get().sceneTransitions(SceneContainerNames.SYSTEM_UI_DEFAULT),
+                mSceneInteractor.get().getTransitions(),
                 sceneTransitionModel -> {
                     if (sceneTransitionModel != null
                             && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
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/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index a04d13b..8e92941 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;
@@ -51,6 +52,10 @@
 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;
@@ -62,6 +67,9 @@
 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;
@@ -91,6 +99,7 @@
     private final FeatureFlags mFeatureFlags;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final Rect mClipBounds = new Rect();
+    private final KeyguardInteractor mKeyguardInteractor;
 
     private Boolean mStatusViewCentered = true;
 
@@ -122,6 +131,7 @@
             KeyguardLogger logger,
             FeatureFlags featureFlags,
             InteractionJankMonitor interactionJankMonitor,
+            KeyguardInteractor keyguardInteractor,
             DumpManager dumpManager) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
@@ -134,12 +144,34 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
+        mKeyguardInteractor = keyguardInteractor;
     }
 
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
         mDumpManager.registerDumpable(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
@@ -308,6 +340,7 @@
     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onTimeChanged() {
+            Slog.v(TAG, "onTimeChanged");
             refreshTime();
         }
 
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/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 6c8f8f3..4a31f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -124,7 +124,7 @@
     private float mScale;
 
     /**
-     * MagnificationFrame represents the bound of {@link #mMirrorSurface} and is constrained
+     * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained
      * by the {@link #mMagnificationFrameBoundary}.
      * We use MagnificationFrame to calculate the position of {@link #mMirrorView}.
      * We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and
@@ -225,6 +225,8 @@
     private boolean mAllowDiagonalScrolling = false;
     private boolean mEditSizeEnable = false;
     private boolean mSettingsPanelVisibility = false;
+    @VisibleForTesting
+    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
 
     @Nullable
     private final MirrorWindowControl mMirrorWindowControl;
@@ -249,6 +251,7 @@
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
 
         final Display display = mContext.getDisplay();
         mDisplayId = mContext.getDisplayId();
@@ -272,8 +275,8 @@
                 com.android.internal.R.integer.config_shortAnimTime);
         updateDimensions();
 
-        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
-        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(),
+        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+        setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
                 mWindowBounds.width() / 2, mWindowBounds.height() / 2);
         computeBounceAnimationScale();
 
@@ -381,12 +384,16 @@
         if (!mMagnificationSizeScaleOptions.contains(index)) {
             return;
         }
-        final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
-        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
-        int size = (int) (initSize * scale);
+        int size = getMagnificationWindowSizeFromIndex(index);
         setWindowSize(size, size);
     }
 
+    int getMagnificationWindowSizeFromIndex(@MagnificationSize int index) {
+        final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
+        int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+        return (int) (initSize * scale) - (int) (initSize * scale) % 2;
+    }
+
     void setEditMagnifierSizeMode(boolean enable) {
         mEditSizeEnable = enable;
         applyResourcesValues();
@@ -395,6 +402,12 @@
             updateDimensions();
             applyTapExcludeRegion();
         }
+
+        if (!enable) {
+            // Keep the magnifier size when exiting edit mode
+            mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+                    new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
+        }
     }
 
     void setDiagonalScrolling(boolean enable) {
@@ -519,12 +532,12 @@
             return false;
         }
         mWindowBounds.set(currentWindowBounds);
-        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
+        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
         final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
         final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
 
-        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), (int) newCenterX,
-                (int) newCenterY);
+        setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
+                (int) newCenterX, (int) newCenterY);
         calculateMagnificationFrameBoundary();
         return true;
     }
@@ -738,6 +751,8 @@
     }
 
     private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+
         // Sets the initial frame area for the mirror and place it to the given center on the
         // display.
         final int initX = centerX - width / 2;
@@ -745,12 +760,18 @@
         mMagnificationFrame.set(initX, initY, initX + width, initY + height);
     }
 
-    private Size getDefaultWindowSizeWithWindowBounds(Rect windowBounds) {
-        int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2;
-        initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size),
-                initSize);
-        initSize += 2 * mMirrorSurfaceMargin;
-        return new Size(initSize, initSize);
+    private Size restoreMagnificationWindowFrameSizeIfPossible() {
+        if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) {
+            return getDefaultMagnificationWindowFrameSize();
+        }
+
+        return mWindowMagnificationSizePrefs.getSizeForCurrentDensity();
+    }
+
+    private Size getDefaultMagnificationWindowFrameSize() {
+        final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM)
+                - 2 * mMirrorSurfaceMargin;
+        return new Size(defaultSize, defaultSize);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
new file mode 100644
index 0000000..4d7ad264
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.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 com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Size;
+
+/**
+ * Class to handle SharedPreference for window magnification size.
+ */
+public final class WindowMagnificationSizePrefs {
+
+    private static final String WINDOW_MAGNIFICATION_PREFERENCES =
+            "window_magnification_preferences";
+    Context mContext;
+    SharedPreferences mWindowMagnificationSizePreferences;
+
+    public WindowMagnificationSizePrefs(Context context) {
+        mContext = context;
+        mWindowMagnificationSizePreferences = mContext
+                .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Uses smallest screen width DP as the key for preference.
+     */
+    private String getKey() {
+        return String.valueOf(
+                mContext.getResources().getConfiguration().smallestScreenWidthDp);
+    }
+
+    /**
+     * Saves the window frame size for current screen density.
+     */
+    public void saveSizeForCurrentDensity(Size size) {
+        mWindowMagnificationSizePreferences.edit()
+                .putString(getKey(), size.toString()).apply();
+    }
+
+    /**
+     * Check if there is a preference saved for current screen density.
+     *
+     * @return true if there is a preference saved for current screen density, false if it is unset.
+     */
+    public boolean isPreferenceSavedForCurrentDensity() {
+        return mWindowMagnificationSizePreferences.contains(getKey());
+    }
+
+    /**
+     * Gets the size preference for current screen density.
+     */
+    public Size getSizeForCurrentDensity() {
+        return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index e60d4e1..0c7d56f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -100,6 +100,9 @@
             )
         } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
+            iconView.contentDescription = context.getString(
+                    R.string.keyguard_face_failed
+            )
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index a1b15f44..d82f458 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -122,9 +122,7 @@
         if (shouldAnimateIconViewForTransition(lastState, newState)) {
             iconView.playAnimation()
         }
-        if (isSideFps) {
-            LottieColorUtils.applyDynamicColors(context, iconView)
-        }
+        LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
     override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ebff0b0..39a45f7 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;
@@ -234,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() {
@@ -926,12 +930,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);
+            }
         }
     }
 
@@ -1024,12 +1040,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/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 9bbf1ef..b68b921 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.text.method.ScrollingMovementMethod
 import android.util.Log
 import android.view.View
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
 import android.widget.Button
 import android.widget.TextView
@@ -335,6 +336,13 @@
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
+                        // Disable background view for cancelling authentication once authenticated,
+                        // and remove from talkback
+                        if (authState.isAuthenticated) {
+                            backgroundView.setOnClickListener(null)
+                            backgroundView.importantForAccessibility =
+                                IMPORTANT_FOR_ACCESSIBILITY_NO
+                        }
                         if (authState.isAuthenticatedAndConfirmed) {
                             view.announceForAccessibility(
                                 view.resources.getString(R.string.biometric_dialog_authenticated)
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 8e14237..d8cf398 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
@@ -24,6 +24,7 @@
 import com.android.systemui.authentication.shared.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
@@ -31,9 +32,7 @@
 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 javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,8 +43,9 @@
 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,
@@ -53,7 +53,6 @@
     private val authenticationInteractor: AuthenticationInteractor,
     private val sceneInteractor: SceneInteractor,
     featureFlags: FeatureFlags,
-    @Assisted private val containerName: String,
 ) {
 
     /** The user-facing message to show in the bouncer. */
@@ -118,23 +117,19 @@
     /**
      * 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,
     ) {
         applicationScope.launch {
             if (authenticationInteractor.isAuthenticationRequired()) {
                 repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
                 sceneInteractor.setCurrentScene(
-                    containerName = containerName,
                     scene = SceneModel(SceneKey.Bouncer),
                 )
             } else {
                 sceneInteractor.setCurrentScene(
-                    containerName = containerName,
                     scene = SceneModel(SceneKey.Gone),
                 )
             }
@@ -180,7 +175,6 @@
 
         if (isAuthenticated) {
             sceneInteractor.setCurrentScene(
-                containerName = containerName,
                 scene = SceneModel(SceneKey.Gone),
             )
         } else {
@@ -228,11 +222,4 @@
             else -> ""
         }
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): BouncerInteractor
-    }
 }
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
index 34e934b..d9ec5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -120,6 +120,8 @@
                         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.reset()
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
                                     securityContainerController.onBouncerVisibilityChanged(
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 a4ef5ce..68e1a29 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
@@ -22,13 +22,12 @@
 import com.android.systemui.R
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 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.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.kotlin.pairwise
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,17 +44,15 @@
 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,
+    private val interactor: BouncerInteractor,
     featureFlags: FeatureFlags,
-    @Assisted containerName: String,
 ) {
-    private val interactor: BouncerInteractor = interactorFactory.create(containerName)
-
     private val isInputEnabled: StateFlow<Boolean> =
         interactor.isThrottled
             .map { !it }
@@ -222,11 +219,4 @@
          */
         val isUpdateAnimated: Boolean,
     )
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): BouncerViewModel
-    }
 }
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 b387e4a..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
 
@@ -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 59fa7f5..23721c9 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()
@@ -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/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 3713811..a7e9efd8 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,9 @@
 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.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -60,8 +57,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
     private var pendingAction: Action? = null
@@ -77,9 +72,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) {
@@ -169,7 +161,6 @@
     override fun runPendingAction(controlId: String) {
         if (isLocked) return
         if (pendingAction?.controlId == controlId) {
-            showSettingsDialogIfNeeded(pendingAction!!)
             pendingAction?.invoke()
             pendingAction = null
         }
@@ -208,7 +199,6 @@
                 true
             }, { pendingAction = null }, true /* afterKeyguardGone */)
         } else {
-            showSettingsDialogIfNeeded(action)
             action.invoke()
         }
     }
@@ -243,15 +233,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/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 557dcf4..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,9 +65,7 @@
     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)
 
@@ -77,7 +74,7 @@
                 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 25eae20..16bb75d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -506,30 +506,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
+                ))
+            }
+            if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+                add(OverflowMenuAdapter.MenuItem(
+                        context.getText(R.string.controls_menu_remove),
+                        REMOVE_APP_ID,
                 ))
             }
             if (!isPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 553405f..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,
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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index f3339c0..90b27c7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -62,11 +62,12 @@
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "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")
+            releasedFlag(119, "notification_memory_logging_enabled")
 
     // TODO(b/260335638): Tracking Bug
     @JvmField
@@ -239,7 +240,7 @@
 
     /** 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", teamfood = true)
+    @JvmField val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
 
     /** Keyguard Migration */
 
@@ -282,6 +283,15 @@
     // TODO(b/291767565): Tracking bug.
     @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
 
+    /** Enables preview loading animation in the wallpaper picker. */
+    // TODO(b/274443705): Tracking Bug
+    @JvmField
+    val WALLPAPER_PICKER_PREVIEW_ANIMATION =
+            unreleasedFlag(
+                    244,
+                    "wallpaper_picker_preview_animation"
+            )
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -382,6 +392,9 @@
     // 804 - monochromatic themes
     @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
 
+    // TODO(b/293380347): Tracking Bug
+    @JvmField val COLOR_FIDELITY = unreleasedFlag(805, "color_fidelity")
+
     // 900 - media
     // TODO(b/254512697): Tracking Bug
     val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
@@ -613,13 +626,8 @@
     @JvmField val NOTE_TASKS = releasedFlag(1900, "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
@@ -701,7 +709,7 @@
     // TODO(b/285059790) : Tracking Bug
     @JvmField
     val LOCKSCREEN_WALLPAPER_DREAM_ENABLED =
-        unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream")
+        unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream", teamfood = true)
 
     // TODO(b/283084712): Tracking Bug
     @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
@@ -731,4 +739,12 @@
     // TODO(b/290213663): Tracking Bug
     @JvmField
     val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration")
+
+    /** Enable the Compose implementation of the PeopleSpaceActivity. */
+    @JvmField
+    val COMPOSE_PEOPLE_SPACE = unreleasedFlag(293570761, "compose_people_space")
+
+    /** Enable the Compose implementation of the Quick Settings footer actions. */
+    @JvmField
+    val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag(293569320, "compose_qs_footer_actions")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 82d0c8b..66de371 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -100,6 +100,7 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -173,6 +174,8 @@
 import dagger.Lazy;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -257,6 +260,22 @@
     private static final int SYSTEM_READY = 18;
     private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
 
+    /** Enum for reasons behind updating wakeAndUnlock state. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+            value = {
+                    WakeAndUnlockUpdateReason.HIDE,
+                    WakeAndUnlockUpdateReason.SHOW,
+                    WakeAndUnlockUpdateReason.FULFILL,
+                    WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK,
+            })
+    @interface WakeAndUnlockUpdateReason {
+        int HIDE = 0;
+        int SHOW = 1;
+        int FULFILL = 2;
+        int WAKE_AND_UNLOCK = 3;
+    }
+
     /**
      * The default amount of time we stay awake (used for all key input)
      */
@@ -808,7 +827,7 @@
             // dreaming. It's time to wake up.
             if (mUnlockingAndWakingFromDream) {
                 Log.d(TAG, "waking from dream after unlock");
-                mUnlockingAndWakingFromDream = false;
+                setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.FULFILL);
 
                 if (mKeyguardStateController.isShowing()) {
                     Log.d(TAG, "keyguard showing after keyguardGone, dismiss");
@@ -2693,7 +2712,7 @@
 
             mKeyguardExitAnimationRunner = null;
             mWakeAndUnlocking = false;
-            mUnlockingAndWakingFromDream = false;
+            setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW);
             setPendingLock(false);
 
             // Force if we we're showing in the middle of hiding, to ensure we end up in the correct
@@ -2799,6 +2818,51 @@
         tryKeyguardDone();
     };
 
+    private void setUnlockAndWakeFromDream(boolean updatedValue,
+            @WakeAndUnlockUpdateReason int reason) {
+        if (updatedValue == mUnlockingAndWakingFromDream) {
+            return;
+        }
+
+        final String reasonDescription;
+
+        switch(reason) {
+            case WakeAndUnlockUpdateReason.FULFILL:
+                reasonDescription = "fulfilling existing request";
+                break;
+            case WakeAndUnlockUpdateReason.HIDE:
+                reasonDescription = "hiding keyguard";
+                break;
+            case WakeAndUnlockUpdateReason.SHOW:
+                reasonDescription = "showing keyguard";
+                break;
+            case WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK:
+                reasonDescription = "waking to unlock";
+                break;
+            default:
+                throw new IllegalStateException("Unexpected value: " + reason);
+        }
+
+        final boolean unsetUnfulfilled = !updatedValue
+                && reason != WakeAndUnlockUpdateReason.FULFILL;
+
+        mUnlockingAndWakingFromDream = updatedValue;
+
+        final String description;
+
+        if (unsetUnfulfilled) {
+            description = "Interrupting request to wake and unlock";
+        } else if (mUnlockingAndWakingFromDream) {
+            description = "Initiating request to wake and unlock";
+        } else {
+            description = "Fulfilling request to wake and unlock";
+        }
+
+        Log.d(TAG, String.format(
+                "Updating waking and unlocking request to %b. description:[%s]. reason:[%s]",
+                mUnlockingAndWakingFromDream,  description, reasonDescription));
+    }
+
     /**
      * Handle message sent by {@link #hideLocked()}
      * @see #HIDE
@@ -2816,8 +2880,11 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleHide");
 
-            mUnlockingAndWakingFromDream = mStatusBarStateController.isDreaming()
-                && !mStatusBarStateController.isDozing();
+            // If waking and unlocking, waking from dream has been set properly.
+            if (!mWakeAndUnlocking) {
+                setUnlockAndWakeFromDream(mStatusBarStateController.isDreaming()
+                        && mPM.isInteractive(), WakeAndUnlockUpdateReason.HIDE);
+            }
 
             if ((mShowing && !mOccluded) || mUnlockingAndWakingFromDream) {
                 if (mUnlockingAndWakingFromDream) {
@@ -3224,7 +3291,8 @@
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
-            if (mPowerGestureIntercepted && mOccluded && isSecure()) {
+            if (mPowerGestureIntercepted && mOccluded && isSecure()
+                    && mUpdateMonitor.isFaceEnrolled()) {
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
@@ -3319,9 +3387,14 @@
         }
     }
 
-    public void onWakeAndUnlocking() {
+    /**
+     * Informs the keyguard view mediator that the device is waking and unlocking.
+     * @param fromDream Whether waking and unlocking is happening over an interactive dream.
+     */
+    public void onWakeAndUnlocking(boolean fromDream) {
         Trace.beginSection("KeyguardViewMediator#onWakeAndUnlocking");
         mWakeAndUnlocking = true;
+        setUnlockAndWakeFromDream(fromDream, WakeAndUnlockUpdateReason.WAKE_AND_UNLOCK);
 
         mKeyguardViewControllerLazy.get().notifyKeyguardAuthenticated(/* primaryAuth */ false);
         userActivity();
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 6fd3e21..30f8f3e 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
@@ -110,6 +110,18 @@
     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.
      *
      * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
@@ -186,6 +198,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)
 
@@ -329,11 +350,7 @@
                     "isFaceAuthenticationEnabled",
                     tableLogBuffer
                 ),
-                logAndObserve(
-                    userRepository.userSwitchingInProgress.isFalse(),
-                    "userSwitchingNotInProgress",
-                    tableLogBuffer
-                ),
+                logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
                 logAndObserve(
                     keyguardRepository.isKeyguardGoingAway.isFalse(),
                     "keyguardNotGoingAway",
@@ -454,7 +471,6 @@
         }
 
     private fun handleFaceCancellationError() {
-        cancelNotReceivedHandlerJob?.cancel()
         applicationScope.launch {
             faceAuthRequestedWhileCancellation?.let {
                 faceAuthLogger.launchingQueuedFaceAuthRequest(it)
@@ -483,6 +499,7 @@
     }
 
     private fun onFaceAuthRequestCompleted() {
+        cancelNotReceivedHandlerJob?.cancel()
         cancellationInProgress = false
         _isAuthRunning.value = false
         authCancellationSignal = null
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 9ee9902..f1b3441 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
@@ -47,16 +47,15 @@
 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.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
@@ -177,7 +176,7 @@
     val keyguardRootViewVisibility: Flow<KeyguardRootViewVisibilityState>
 
     /** Receive an event for doze time tick */
-    val dozeTimeTick: Flow<Unit>
+    val dozeTimeTick: Flow<Long>
 
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
@@ -248,6 +247,7 @@
     private val dreamOverlayCallbackController: DreamOverlayCallbackController,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
+    private val systemClock: SystemClock,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -398,11 +398,11 @@
         _isDozing.value = isDozing
     }
 
-    private val _dozeTimeTick = MutableSharedFlow<Unit>()
-    override val dozeTimeTick = _dozeTimeTick.asSharedFlow()
+    private val _dozeTimeTick = MutableStateFlow<Long>(0)
+    override val dozeTimeTick = _dozeTimeTick.asStateFlow()
 
     override fun dozeTimeTick() {
-        _dozeTimeTick.tryEmit(Unit)
+        _dozeTimeTick.value = systemClock.uptimeMillis()
     }
 
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
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
index 5ef9a9e..e4e6a6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,9 @@
         get() = emptyFlow()
 
     override fun lockoutFaceAuth() = Unit
+    override fun pauseFaceAuth() = Unit
+
+    override fun resumeFaceAuth() = Unit
 
     /**
      * Trigger face authentication.
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/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 98d7434..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
@@ -94,11 +94,16 @@
 
     private fun listenForDreamingToGone() {
         scope.launch {
-            keyguardInteractor.biometricUnlockState.collect { biometricUnlockState ->
-                if (biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM) {
-                    startTransitionTo(KeyguardState.GONE)
+            keyguardInteractor.biometricUnlockState
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { (biometricUnlockState, lastStartedTransitionStep) ->
+                    if (
+                        lastStartedTransitionStep.to == KeyguardState.DREAMING &&
+                            biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-            }
         }
     }
 
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 f82633f..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
@@ -48,6 +48,7 @@
         listenForGoneToAodOrDozing()
         listenForGoneToDreaming()
         listenForGoneToLockscreen()
+        listenForGoneToDreamingLockscreenHosted()
     }
 
     // Primarily for when the user chooses to lock down the device
@@ -63,12 +64,35 @@
         }
     }
 
+    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)
+                    }
+                }
+        }
+    }
+
     private fun listenForGoneToDreaming() {
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { (isAbleToDream, lastStartedStep) ->
-                    if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
+                .sample(
+                    combine(
+                        transitionInteractor.startedKeyguardTransitionStep,
+                        keyguardInteractor.isActiveDreamLockscreenHosted,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (isAbleToDream, lastStartedStep, isActiveDreamLockscreenHosted) ->
+                    if (
+                        isAbleToDream &&
+                            lastStartedStep.to == KeyguardState.GONE &&
+                            !isActiveDreamLockscreenHosted
+                    ) {
                         startTransitionTo(KeyguardState.DREAMING)
                     }
                 }
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 ed1bf3e..6b28b27 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
@@ -70,17 +70,27 @@
                     combine(
                         transitionInteractor.startedKeyguardTransitionStep,
                         transitionInteractor.finishedKeyguardState,
-                        ::Pair
+                        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)) {
-                        startTransitionTo(KeyguardState.DREAMING)
+                        if (isActiveDreamLockscreenHosted) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        } else {
+                            startTransitionTo(KeyguardState.DREAMING)
+                        }
                     }
                 }
         }
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 e1754f5..9142d1f 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
@@ -27,6 +27,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 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 javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -52,6 +54,7 @@
         listenForPrimaryBouncerToGone()
         listenForPrimaryBouncerToAodOrDozing()
         listenForPrimaryBouncerToLockscreenOrOccluded()
+        listenForPrimaryBouncerToDreamingLockscreenHosted()
     }
 
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
@@ -62,17 +65,24 @@
                         keyguardInteractor.wakefulnessModel,
                         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
                     ) {
                         startTransitionTo(
                             if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
@@ -111,6 +121,30 @@
         }
     }
 
+    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() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
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 cc15916..53d3c07 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
@@ -34,9 +34,9 @@
 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.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -87,7 +87,7 @@
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
     /** Receive an event for doze time tick */
-    val dozeTimeTick: Flow<Unit> = repository.dozeTimeTick
+    val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
     /** Whether Always-on Display mode is available. */
     val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
     /** Doze transition information. */
@@ -100,7 +100,7 @@
     /** 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: Flow<Boolean> = repository.isActiveDreamLockscreenHosted
+    val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
     /** Event for when the camera gesture is detected */
     val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> = conflatedCallbackFlow {
         val callback =
@@ -122,6 +122,9 @@
     /** The device wake/sleep state */
     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
      * that doze mode is not running and DREAMING is ok to commence.
@@ -237,8 +240,16 @@
         repository.setQuickSettingsVisible(isVisible)
     }
 
-    fun setKeyguardRootVisibility(statusBarState: Int, goingToFullShade: Boolean, isOcclusionTransitionRunning: Boolean) {
-        repository.setKeyguardVisibility(statusBarState, goingToFullShade, isOcclusionTransitionRunning)
+    fun setKeyguardRootVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        isOcclusionTransitionRunning: Boolean
+    ) {
+        repository.setKeyguardVisibility(
+            statusBarState,
+            goingToFullShade,
+            isOcclusionTransitionRunning
+        )
     }
 
     fun setClockPosition(x: Int, y: Int) {
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 a486843..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
@@ -412,7 +412,11 @@
             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)
+            ),
         )
     }
 
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 45bf20d..8c4c7ae 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
@@ -24,6 +24,7 @@
 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.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
@@ -89,12 +90,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)
 
@@ -102,6 +111,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)
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 4244e55..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
@@ -92,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
index 1c200b0..278c68d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -19,10 +19,9 @@
 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.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-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
@@ -30,17 +29,14 @@
 import kotlinx.coroutines.flow.stateIn
 
 /** Hosts business and application state accessing logic for the lockscreen scene. */
+@SysUISingleton
 class LockscreenSceneInteractor
-@AssistedInject
+@Inject
 constructor(
     @Application applicationScope: CoroutineScope,
     private val authenticationInteractor: AuthenticationInteractor,
-    bouncerInteractorFactory: BouncerInteractor.Factory,
-    @Assisted private val containerName: String,
+    private val bouncerInteractor: BouncerInteractor,
 ) {
-    private val bouncerInteractor: BouncerInteractor =
-        bouncerInteractorFactory.create(containerName)
-
     /** Whether the device is currently locked. */
     val isDeviceLocked: StateFlow<Boolean> =
         authenticationInteractor.isUnlocked
@@ -67,13 +63,6 @@
 
     /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
     fun dismissLockscreen() {
-        bouncerInteractor.showOrUnlockDevice(containerName = containerName)
-    }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): LockscreenSceneInteractor
+        bouncerInteractor.showOrUnlockDevice()
     }
 }
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 8f4776f..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
@@ -35,6 +35,7 @@
 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
@@ -49,6 +50,7 @@
 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
@@ -69,6 +71,7 @@
     private val faceAuthenticationLogger: FaceAuthenticationLogger,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val userRepository: UserRepository,
 ) : CoreStartable, KeyguardFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
@@ -128,6 +131,23 @@
                 }
             }
             .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() {
@@ -199,8 +219,10 @@
             } else {
                 faceAuthenticationStatusOverride.value = null
                 applicationScope.launch {
-                    faceAuthenticationLogger.authRequested(uiEvent)
-                    repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+                    withContext(mainDispatcher) {
+                        faceAuthenticationLogger.authRequested(uiEvent)
+                        repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+                    }
                 }
             }
         } else {
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/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/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index f212a55..abd178c 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
@@ -19,12 +19,11 @@
 import com.android.systemui.R
 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.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -32,15 +31,13 @@
 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,
+    private val interactor: LockscreenSceneInteractor,
 ) {
-    private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName)
-
     /** The icon for the "lock" button on the lockscreen. */
     val lockButtonIcon: StateFlow<Icon> =
         interactor.isDeviceLocked
@@ -98,11 +95,4 @@
                 )
         )
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): LockscreenSceneViewModel
-    }
 }
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
index b307f1b..dd58607 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt
@@ -112,6 +112,7 @@
             KeyguardState.OFF,
             KeyguardState.DOZING,
             KeyguardState.DREAMING,
+            KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
             KeyguardState.AOD,
             KeyguardState.PRIMARY_BOUNCER,
             KeyguardState.GONE,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 42164c7..fdb3ddd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -30,16 +30,11 @@
 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.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider
+import com.android.intentresolver.ChooserActivity
+import com.android.intentresolver.chooser.TargetInfo
 import com.android.internal.annotations.VisibleForTesting
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider
-import com.android.internal.app.ChooserActivity
-import com.android.internal.app.ResolverListController
-import com.android.internal.app.chooser.NotSelectableTargetInfo
-import com.android.internal.app.chooser.TargetInfo
 import com.android.systemui.R
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
@@ -56,12 +51,8 @@
     private val activityLauncher: AsyncActivityLauncher,
     /** This is used to override the dependency in a screenshot test */
     @VisibleForTesting
-    private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
-) :
-    ChooserActivity(),
-    MediaProjectionAppSelectorView,
-    MediaProjectionAppSelectorResultHandler,
-    LifecycleOwner {
+    private val listControllerFactory: ((userHandle: UserHandle) -> ChooserListController)?
+) : ChooserActivity(), MediaProjectionAppSelectorView, MediaProjectionAppSelectorResultHandler {
 
     @Inject
     constructor(
@@ -69,8 +60,6 @@
         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
@@ -84,7 +73,6 @@
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
     public override fun onCreate(bundle: Bundle?) {
-        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component = componentFactory.create(view = this, resultHandler = this)
         component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
@@ -107,26 +95,6 @@
         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)
@@ -137,13 +105,13 @@
     override fun createBlockerEmptyStateProvider(): EmptyStateProvider =
         component.emptyStateProvider
 
-    override fun createListController(userHandle: UserHandle): ResolverListController =
+    override fun createListController(userHandle: UserHandle): ChooserListController =
         listControllerFactory?.invoke(userHandle) ?: super.createListController(userHandle)
 
     override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
         val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
         val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
-        if (targetInfo is NotSelectableTargetInfo) return
+        if (targetInfo.isNotSelectableTargetInfo) return
 
         val intent = createIntent(targetInfo)
 
@@ -183,7 +151,6 @@
     }
 
     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
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index f908481..785a1e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -18,8 +18,8 @@
 
 import android.content.Context
 import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.media.InfoMediaManager
 import com.android.settingslib.media.LocalMediaManager
-import com.android.settingslib.media.ManagerInfoMediaManager
 import javax.inject.Inject
 
 /** Factory to create [LocalMediaManager] objects. */
@@ -31,8 +31,7 @@
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
     fun create(packageName: String): LocalMediaManager {
-        return ManagerInfoMediaManager(context, packageName, null, localBluetoothManager).run {
-            LocalMediaManager(context, localBluetoothManager, this, packageName)
-        }
+        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+            .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
     }
 }
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 83631b0..be42569 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -76,7 +76,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.ManagerInfoMediaManager;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
@@ -194,7 +193,7 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
-        InfoMediaManager imm = new ManagerInfoMediaManager(mContext, packageName, null, lbm);
+        InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
index 829b0dd..fd14e2b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionBlockerEmptyStateProvider.kt
@@ -17,10 +17,10 @@
 
 import android.content.Context
 import android.os.UserHandle
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState
+import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider
+import com.android.intentresolver.ResolverListAdapter
 import com.android.internal.R as AndroidR
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider
-import com.android.internal.app.ResolverListAdapter
 import com.android.systemui.R
 import com.android.systemui.mediaprojection.devicepolicy.PersonalProfile
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 7f0f894..d1d3e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -28,6 +28,8 @@
 import androidx.lifecycle.ViewModelProvider;
 
 import com.android.systemui.compose.ComposeFacade;
+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.viewmodel.PeopleViewModel;
 
@@ -43,11 +45,14 @@
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
 
     private final PeopleViewModel.Factory mViewModelFactory;
+    private final FeatureFlags mFeatureFlags;
 
     @Inject
-    public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
+    public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory,
+            FeatureFlags featureFlags) {
         super();
         mViewModelFactory = viewModelFactory;
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -67,7 +72,8 @@
             return null;
         };
 
-        if (ComposeFacade.INSTANCE.isComposeAvailable()) {
+        if (mFeatureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)
+                && ComposeFacade.INSTANCE.isComposeAvailable()) {
             Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
             ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index a2b2a89..d801faa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,6 +52,7 @@
 import com.android.systemui.compose.ComposeFacade;
 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.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
@@ -285,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);
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/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 36dec1d..5e6a44b 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,19 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
 
 /** Models UI state and handles user input for the quick settings scene. */
+@SysUISingleton
 class QuickSettingsSceneViewModel
-@AssistedInject
+@Inject
 constructor(
-    lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
-    @Assisted containerName: String,
+    private val lockscreenSceneInteractor: LockscreenSceneInteractor,
 ) {
-    private val lockscreenSceneInteractor: LockscreenSceneInteractor =
-        lockscreenSceneInteractorFactory.create(containerName)
-
     /** Notifies that some content in quick settings was clicked. */
     fun onContentClicked() {
         lockscreenSceneInteractor.dismissLockscreen()
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): QuickSettingsSceneViewModel
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index bf40a2d..03bd11b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -97,7 +97,6 @@
 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.scene.shared.model.SceneContainerNames;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
@@ -221,8 +220,7 @@
                             // 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(
-                                        SceneContainerNames.SYSTEM_UI_DEFAULT, true);
+                                mSceneInteractor.get().setVisible(true);
                             } else {
                                 centralSurfaces.onInputFocusTransfer(
                                         mInputFocusTransferStarted, false /* cancel */,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 0a9839e..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.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/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 0a86d35..1fca488 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
@@ -29,26 +29,20 @@
 class SceneContainerRepository
 @Inject
 constructor(
-    private val containerConfigByName: Map<String, SceneContainerConfig>,
+    private val config: SceneContainerConfig,
 ) {
 
-    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 sceneTransitionByContainerName:
-        Map<String, MutableStateFlow<SceneTransitionModel?>> =
-        containerConfigByName.keys.associateWith { MutableStateFlow(null) }
+    private val _isVisible = MutableStateFlow(true)
+    val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
+
+    private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey))
+    val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow()
+
+    private val _transitionProgress = MutableStateFlow(1f)
+    val transitionProgress: StateFlow<Float> = _transitionProgress.asStateFlow()
+
+    private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
+    val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
 
     /**
      * Returns the keys to all scenes in the container with the given name.
@@ -56,100 +50,50 @@
      * 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 setCurrentScene(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 current scene key to "${scene.key}". The configuration does not contain a
+                scene with that key.
             """
                 .trimIndent()
         }
 
-        currentSceneByContainerName.setValue(containerName, scene)
+        _currentScene.value = scene
     }
 
     /** Sets the scene transition in the container with the given name. */
-    fun setSceneTransition(containerName: String, from: SceneKey, to: SceneKey) {
-        check(allSceneKeys(containerName).contains(from)) {
+    fun setSceneTransition(from: SceneKey, to: SceneKey) {
+        check(allSceneKeys().contains(from)) {
             """
-                Cannot set current scene key to "$from". The container "$containerName" does
-                not contain a scene with that key.
+                Cannot set current scene key to "$from". The configuration does not contain a scene
+                with that key.
             """
                 .trimIndent()
         }
-        check(allSceneKeys(containerName).contains(to)) {
+        check(allSceneKeys().contains(to)) {
             """
-                Cannot set current scene key to "$to". The container "$containerName" does
-                not contain a scene with that key.
+                Cannot set current scene key to "$to". The configuration does not contain a scene
+                with that key.
             """
                 .trimIndent()
         }
 
-        sceneTransitionByContainerName.setValue(
-            containerName,
-            SceneTransitionModel(from = from, to = to)
-        )
-    }
-
-    /** The current scene in the container with the given name. */
-    fun currentScene(containerName: String): StateFlow<SceneModel> {
-        return currentSceneByContainerName.mutableOrError(containerName).asStateFlow()
-    }
-
-    /**
-     * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
-     * transition occurs. The flow begins with a `null` value at first, because the initial scene is
-     * not something that we transition to from another scene.
-     */
-    fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
-        return sceneTransitionByContainerName.mutableOrError(containerName).asStateFlow()
+        _transitions.value = SceneTransitionModel(from = from, to = to)
     }
 
     /** 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()
+    fun setVisible(isVisible: Boolean) {
+        _isVisible.value = isVisible
     }
 
     /** 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()
+    fun setSceneTransitionProgress(progress: Float) {
+        _transitionProgress.value = progress
     }
 }
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 f03f040..39daad3 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
@@ -30,12 +30,8 @@
 /**
  * Generic business logic and app state accessors for the scene framework.
  *
- * Note that scene container specific business logic does not belong in this class. Instead, it
- * should be hoisted to a class that is specific to that scene container, for an example, please see
- * [SystemUiDefaultSceneContainerStartable].
- *
- * Also 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
+ * 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
@@ -51,50 +47,42 @@
      * 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) {
-        val currentSceneKey = repository.currentScene(containerName).value.key
-        repository.setCurrentScene(containerName, scene)
-        repository.setSceneTransition(containerName, from = currentSceneKey, to = scene.key)
+    fun setCurrentScene(scene: SceneModel) {
+        val currentSceneKey = repository.currentScene.value.key
+        repository.setCurrentScene(scene)
+        repository.setSceneTransition(from = currentSceneKey, to = scene.key)
     }
 
     /** The current scene in the container with the given name. */
-    fun currentScene(containerName: String): StateFlow<SceneModel> {
-        return repository.currentScene(containerName)
-    }
+    val currentScene: StateFlow<SceneModel> = repository.currentScene
 
     /** Sets the visibility of the container with the given name. */
-    fun setVisible(containerName: String, isVisible: Boolean) {
-        return repository.setVisible(containerName, isVisible)
+    fun setVisible(isVisible: Boolean) {
+        return repository.setVisible(isVisible)
     }
 
     /** Whether the container with the given name is visible. */
-    fun isVisible(containerName: String): StateFlow<Boolean> {
-        return repository.isVisible(containerName)
-    }
+    val isVisible: StateFlow<Boolean> = repository.isVisible
 
     /** 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)
+    fun setSceneTransitionProgress(progress: Float) {
+        repository.setSceneTransitionProgress(progress)
     }
 
     /** 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)
-    }
+    val transitionProgress: StateFlow<Float> = repository.transitionProgress
 
     /**
      * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
      * transition occurs. The flow begins with a `null` value at first, because the initial scene is
      * not something that we transition to from another scene.
      */
-    fun sceneTransitions(containerName: String): StateFlow<SceneTransitionModel?> {
-        return repository.sceneTransitions(containerName)
-    }
+    val transitions: StateFlow<SceneTransitionModel?> = repository.transitions
 
     private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
rename to packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 92384d6..1c87eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -28,7 +28,6 @@
 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.model.SceneContainerNames
 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
@@ -44,12 +43,11 @@
 import kotlinx.coroutines.launch
 
 /**
- * Hooks up business logic that manipulates the state of the [SceneInteractor] for the default
- * system UI scene container (the one named [SceneContainerNames.SYSTEM_UI_DEFAULT]) based on state
- * from other systems.
+ * 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 SystemUiDefaultSceneContainerStartable
+class SceneContainerStartable
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
@@ -72,13 +70,10 @@
     /** Updates the visibility of the scene container based on the current scene. */
     private fun hydrateVisibility() {
         applicationScope.launch {
-            sceneInteractor
-                .currentScene(CONTAINER_NAME)
+            sceneInteractor.currentScene
                 .map { it.key }
                 .distinctUntilChanged()
-                .collect { sceneKey ->
-                    sceneInteractor.setVisible(CONTAINER_NAME, sceneKey != SceneKey.Gone)
-                }
+                .collect { sceneKey -> sceneInteractor.setVisible(sceneKey != SceneKey.Gone) }
         }
     }
 
@@ -87,7 +82,7 @@
         applicationScope.launch {
             authenticationInteractor.isUnlocked
                 .map { isUnlocked ->
-                    val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key
+                    val currentSceneKey = sceneInteractor.currentScene.value.key
                     val isBypassEnabled = authenticationInteractor.isBypassEnabled()
                     when {
                         isUnlocked ->
@@ -135,8 +130,7 @@
     /** Keeps [SysUiState] up-to-date */
     private fun hydrateSystemUiState() {
         applicationScope.launch {
-            sceneInteractor
-                .currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT)
+            sceneInteractor.currentScene
                 .map { it.key }
                 .distinctUntilChanged()
                 .collect { sceneKey ->
@@ -155,12 +149,7 @@
 
     private fun switchToScene(targetSceneKey: SceneKey) {
         sceneInteractor.setCurrentScene(
-            containerName = CONTAINER_NAME,
             scene = SceneModel(targetSceneKey),
         )
     }
-
-    companion object {
-        private const val CONTAINER_NAME = SceneContainerNames.SYSTEM_UI_DEFAULT
-    }
 }
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
index b3de2d1..8da1803 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartableModule.kt
@@ -27,6 +27,6 @@
 
     @Binds
     @IntoMap
-    @ClassKey(SystemUiDefaultSceneContainerStartable::class)
-    fun bind(impl: SystemUiDefaultSceneContainerStartable): CoreStartable
+    @ClassKey(SceneContainerStartable::class)
+    fun bind(impl: SceneContainerStartable): CoreStartable
 }
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
index 0327edb..8204edc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.scene.shared.model
 
-/** Models the configuration of a single scene container. */
+/** Models the configuration of the 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
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
index 7562a5a..f74005b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt
@@ -16,44 +16,27 @@
 
 package com.android.systemui.scene.shared.model
 
-import com.android.systemui.dagger.SysUISingleton
 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,
+    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,
         )
     }
-
-    @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/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 005f48d9..f44748a 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
@@ -17,16 +17,20 @@
 package com.android.systemui.scene.ui.viewmodel
 
 import android.view.MotionEvent
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.RemoteUserInput
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import javax.inject.Inject
 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,
 ) {
     /** A flow of motion events originating from outside of the scene framework. */
     val remoteUserInput: StateFlow<RemoteUserInput?> = interactor.remoteUserInput
@@ -37,22 +41,22 @@
      * 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)
+    val currentScene: StateFlow<SceneModel> = interactor.currentScene
 
     /** 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)
+        interactor.setCurrentScene(scene)
     }
 
     /** Notifies of the progress of a scene transition. */
     fun setSceneTransitionProgress(progress: Float) {
-        interactor.setSceneTransitionProgress(containerName, progress)
+        interactor.setSceneTransitionProgress(progress)
     }
 
     /** Handles a [MotionEvent] representing remote 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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 773f35e..2ea63c2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -140,6 +140,7 @@
 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;
@@ -597,6 +598,9 @@
     private final OccludedToLockscreenTransitionViewModel mOccludedToLockscreenTransitionViewModel;
     private final LockscreenToDreamingTransitionViewModel mLockscreenToDreamingTransitionViewModel;
     private final GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
+    private final GoneToDreamingLockscreenHostedTransitionViewModel
+            mGoneToDreamingLockscreenHostedTransitionViewModel;
+
     private final LockscreenToOccludedTransitionViewModel mLockscreenToOccludedTransitionViewModel;
 
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -605,6 +609,7 @@
     private final CoroutineDispatcher mMainDispatcher;
     private boolean mIsAnyMultiShadeExpanded;
     private boolean mIsOcclusionTransitionRunning = false;
+    private boolean mIsGoneToDreamingLockscreenHostedTransitionRunning;
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mOccludedToLockscreenTransitionTranslationY;
     private int mLockscreenToDreamingTransitionTranslationY;
@@ -652,6 +657,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 =
@@ -734,6 +758,8 @@
             OccludedToLockscreenTransitionViewModel occludedToLockscreenTransitionViewModel,
             LockscreenToDreamingTransitionViewModel lockscreenToDreamingTransitionViewModel,
             GoneToDreamingTransitionViewModel goneToDreamingTransitionViewModel,
+            GoneToDreamingLockscreenHostedTransitionViewModel
+                    goneToDreamingLockscreenHostedTransitionViewModel,
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
@@ -761,6 +787,8 @@
         mOccludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel;
         mLockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel;
         mGoneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel;
+        mGoneToDreamingLockscreenHostedTransitionViewModel =
+                goneToDreamingLockscreenHostedTransitionViewModel;
         mLockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mKeyguardInteractor = keyguardInteractor;
@@ -1091,6 +1119,24 @@
                 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);
@@ -1496,13 +1542,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) {
@@ -1625,6 +1676,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;
@@ -1653,6 +1708,11 @@
         return mDozing && mDozeParameters.getAlwaysOn();
     }
 
+
+    private boolean isActiveDreamLockscreenHosted() {
+        return mKeyguardInteractor.isActiveDreamLockscreenHosted().getValue();
+    }
+
     private boolean hasVisibleNotifications() {
         return mNotificationStackScrollLayoutController
                 .getVisibleNotificationCount() != 0
@@ -2820,7 +2880,9 @@
 
     @Override
     public void onScreenTurningOn() {
-        mKeyguardStatusViewController.dozeTimeTick();
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            mKeyguardStatusViewController.dozeTimeTick();
+        }
     }
 
     private void onMiddleClicked() {
@@ -3070,10 +3132,11 @@
         }
     }
 
-    @Override
     public void dozeTimeTick() {
         mLockIconViewController.dozeTimeTick();
-        mKeyguardStatusViewController.dozeTimeTick();
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            mKeyguardStatusViewController.dozeTimeTick();
+        }
         if (mInterpolatedDarkAmount > 0) {
             positionClockAndNotifications();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index e02c427..2955118 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -36,7 +36,6 @@
 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.shared.model.SceneContainerNames
 import com.android.systemui.scene.ui.view.SceneWindowRootView
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -73,11 +72,8 @@
         fun providesWindowRootView(
             layoutInflater: LayoutInflater,
             featureFlags: FeatureFlags,
-            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
             viewModelProvider: Provider<SceneContainerViewModel>,
-            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
             containerConfigProvider: Provider<SceneContainerConfig>,
-            @Named(SceneContainerNames.SYSTEM_UI_DEFAULT)
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
         ): WindowRootView {
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..0b3ed56 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,12 +16,11 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+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
@@ -29,32 +28,29 @@
 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,
+    private val lockscreenSceneInteractor: LockscreenSceneInteractor,
 ) {
-    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
+        lockscreenSceneInteractor.isDeviceLocked
             .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     upDestinationSceneKey(
-                        isLocked = lockScreenInteractor.isDeviceLocked.value,
+                        isLocked = lockscreenSceneInteractor.isDeviceLocked.value,
                     ),
             )
 
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() {
-        lockScreenInteractor.dismissLockscreen()
+        lockscreenSceneInteractor.dismissLockscreen()
     }
 
     private fun upDestinationSceneKey(
@@ -62,11 +58,4 @@
     ): SceneKey {
         return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
     }
-
-    @AssistedFactory
-    interface Factory {
-        fun create(
-            containerName: String,
-        ): ShadeSceneViewModel
-    }
 }
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 31d4ab9..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
@@ -91,6 +91,7 @@
         NotificationSectionHeadersModule.class,
         NotificationListViewModelModule.class,
         ActivatableNotificationViewModelModule.class,
+        NotificationMemoryModule.class,
 })
 public interface NotificationsModule {
     @Binds
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 f7bd177..106d11f 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
@@ -39,7 +39,6 @@
 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.wm.shell.bubbles.Bubbles
@@ -72,7 +71,6 @@
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
     private val fgsNotifListener: ForegroundServiceNotificationListener,
-    private val memoryMonitor: Lazy<NotificationMemoryMonitor>,
     private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
@@ -108,7 +106,6 @@
         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/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/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 36a8e98..5e7e4be 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
@@ -426,16 +426,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));
@@ -444,7 +449,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);
@@ -452,7 +457,7 @@
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                mWasCancelled = true;
+                mRunWithoutInterruptions = false;
             }
         });
 
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 035bdf1..f6ccaa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -471,7 +471,7 @@
         };
 
         final boolean wakingFromDream = mMode == MODE_WAKE_AND_UNLOCK_FROM_DREAM
-                && !mStatusBarStateController.isDozing();
+                && mPowerManager.isInteractive();
 
         if (mMode != MODE_NONE && !wakingFromDream) {
             wakeUp.run();
@@ -509,7 +509,7 @@
                     // later to awaken.
                 }
                 mNotificationShadeWindowController.setNotificationShadeFocusable(false);
-                mKeyguardViewMediator.onWakeAndUnlocking();
+                mKeyguardViewMediator.onWakeAndUnlocking(wakingFromDream);
                 Trace.endSection();
                 break;
             case MODE_ONLY_WAKE:
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 ed9722e..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;
     }
@@ -317,7 +313,6 @@
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
             ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick();
         }
-        mBurnInInteractor.dozeTimeTick();
     }
 
     @Override
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 8c3050d..1227287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.RemoteUserInput
-import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
@@ -183,7 +182,7 @@
                 sceneInteractor.get()
                     .onRemoteUserInput(RemoteUserInput.translateMotionEvent(event))
                 // TODO(b/291965119): remove once view is expanded to cover the status bar
-                sceneInteractor.get().setVisible(SceneContainerNames.SYSTEM_UI_DEFAULT, true)
+                sceneInteractor.get().setVisible(true)
                 return false
             }
 
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 68a6b3d..fa9b9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,7 +37,6 @@
 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.SceneContainerNames;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -126,7 +125,7 @@
 
         if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
             javaAdapter.get().alwaysCollectFlow(
-                    sceneInteractor.get().isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT),
+                    sceneInteractor.get().isVisible(),
                     this::onShadeExpansionFullyChanged);
         }
 
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/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/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index eef66b6..a983d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -22,83 +22,86 @@
 
 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 06f68c6..5a9f5d5 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -130,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;
@@ -398,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;
@@ -632,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,
@@ -641,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,
@@ -660,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)
@@ -819,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/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/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/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index d39a53d..53f4837 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;
@@ -65,6 +67,7 @@
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.VolumeDialogController;
@@ -131,7 +134,7 @@
     private final Receiver mReceiver = new Receiver();
     private final RingerModeObservers mRingerModeObservers;
     private final MediaSessions mMediaSessions;
-    private final CaptioningManager mCaptioningManager;
+    private CaptioningManager mCaptioningManager;
     private final KeyguardManager mKeyguardManager;
     private final ActivityManager mActivityManager;
     private final UserTracker mUserTracker;
@@ -179,11 +182,11 @@
             AccessibilityManager accessibilityManager,
             PackageManager packageManager,
             WakefulnessLifecycle wakefulnessLifecycle,
-            CaptioningManager captioningManager,
             KeyguardManager keyguardManager,
             ActivityManager activityManager,
             UserTracker userTracker,
-            DumpManager dumpManager
+            DumpManager dumpManager,
+            @Main Handler mainHandler
     ) {
         mContext = context.getApplicationContext();
         mPackageManager = packageManager;
@@ -209,10 +212,12 @@
         mVibrator = vibrator;
         mHasVibrator = mVibrator.hasVibrator();
         mAudioService = iAudioService;
-        mCaptioningManager = captioningManager;
         mKeyguardManager = keyguardManager;
         mActivityManager = activityManager;
         mUserTracker = userTracker;
+        mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mainHandler));
+        createCaptioningManagerServiceByUserContext(mUserTracker.getUserContext());
+
         dumpManager.registerDumpable("VolumeDialogControllerImpl", this);
 
         boolean accessibilityVolumeStreamActive = accessibilityManager
@@ -316,6 +321,25 @@
         mWorker.sendEmptyMessage(W.GET_STATE);
     }
 
+    /**
+     * 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 = userContext.getSystemService(CaptioningManager.class);
+    }
+
     public boolean areCaptionsEnabled() {
         return mCaptioningManager.isSystemAudioCaptioningEnabled();
     }
@@ -719,7 +743,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},
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 3abae6b..e447c29 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -733,29 +733,20 @@
             // is
             // not enough to trigger a dismissal of the keyguard.
             underTest.onViewAttached()
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
             // While listening, going from the bouncer scene to the gone scene, does dismiss the
             // keyguard.
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Gone, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
 
             // While listening, moving back to the bouncer scene does not dismiss the keyguard
             // again.
             clearInvocations(viewMediatorCallback)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
@@ -763,18 +754,12 @@
             // scene
             // does not dismiss the keyguard while we're not listening.
             underTest.onViewDetached()
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Gone, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
             // While not listening, moving back to the bouncer does not dismiss the keyguard.
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null))
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
@@ -782,10 +767,7 @@
             // gone
             // scene now does dismiss the keyguard again.
             underTest.onViewAttached()
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Gone, null)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null))
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
         }
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/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 7114c22..20d9ef1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -23,80 +23,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.dump.DumpManager;
-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;
-
-    @Mock private DumpManager mDumpManager;
-
-    @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,
-                mDumpManager) {
-                    @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() {
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/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 5867a40c..44a2b68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -20,12 +20,13 @@
 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.AnimatorTestRule;
+import androidx.core.animation.ObjectAnimator;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
@@ -37,6 +38,7 @@
 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;
 
@@ -45,6 +47,9 @@
 @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;
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 11ad206..a48fa5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -71,6 +71,7 @@
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.text.TextUtils;
+import android.util.Size;
 import android.view.Display;
 import android.view.IWindowSession;
 import android.view.MotionEvent;
@@ -100,6 +101,7 @@
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -313,10 +315,10 @@
         assertFalse(rects.isEmpty());
     }
 
+    @Ignore("The default window size should be constrained after fixing b/288056772")
     @Test
     public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
-        final int screenSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_max_frame_size) * 10;
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
@@ -543,17 +545,22 @@
     }
 
     @Test
-    public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
         // The default position is at the center of the screen.
         final float expectedRatio = 0.5f;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
+
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
         mWindowManager.setWindowBounds(testWindowBounds);
 
         mInstrumentation.runOnMainSync(() -> {
@@ -568,26 +575,49 @@
                 mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
                 0);
     }
+
     @Test
-    public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
+    public void onScreenChangedToSavedDensity_enabled_restoreSavedMagnifierWindow() {
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        mWindowMagnificationController.mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+                new Size(windowFrameSize, windowFrameSize));
+
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
-        final int screenSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_max_frame_size) * 10;
+
+        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        assertTrue(params.width == windowFrameSize);
+        assertTrue(params.height == windowFrameSize);
+    }
+
+    @Test
+    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
         });
 
-        final int halfScreenSize = screenSize / 2;
+        final int defaultWindowSize =
+                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
+                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
         WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
-        // The frame size should be the half of smaller value of window height/width unless it
-        //exceed the max frame size.
-        assertTrue(params.width < halfScreenSize);
-        assertTrue(params.height < halfScreenSize);
+
+        assertTrue(params.width == defaultWindowSize);
+        assertTrue(params.height == defaultWindowSize);
     }
 
     @Test
@@ -1136,6 +1166,11 @@
         mWindowManager.setWindowInsets(testInsets);
     }
 
+    private int updateMirrorSurfaceMarginDimension() {
+        return mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+    }
+
     @Surface.Rotation
     private int simulateRotateTheDevice() {
         final Display display = Mockito.spy(mContext.getDisplay());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
new file mode 100644
index 0000000..04b0d70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Size;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WindowMagnificationSizePrefsTest extends SysuiTestCase {
+
+    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs =
+            new WindowMagnificationSizePrefs(mContext);
+
+    @Test
+    public void saveSizeForCurrentDensity_getExpectedSize() {
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+        assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity())
+                .isEqualTo(testSize);
+    }
+
+    @Test
+    public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+        assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity())
+                .isTrue();
+    }
+}
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 58982d1..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;
@@ -1128,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);
@@ -1160,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.
@@ -1514,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/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 6babf04..14fc931 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
@@ -69,13 +69,12 @@
     @Test
     fun pinAuthMethod() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
 
@@ -101,14 +100,13 @@
     @Test
     fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
             utils.authenticationRepository.setUnlocked(false)
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
             underTest.clearMessage()
@@ -138,13 +136,12 @@
     @Test
     fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.clearMessage()
 
@@ -168,14 +165,13 @@
     @Test
     fun passwordAuthMethod() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PASSWORD)
 
@@ -201,14 +197,13 @@
     @Test
     fun patternAuthMethod() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
@@ -239,13 +234,12 @@
     @Test
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
 
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -253,12 +247,11 @@
     @Test
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             utils.authenticationRepository.setUnlocked(false)
 
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
@@ -266,8 +259,7 @@
     @Test
     fun showOrUnlockDevice_customMessageShown() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
@@ -275,7 +267,7 @@
             utils.authenticationRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1, customMessage)
+            underTest.showOrUnlockDevice(customMessage)
 
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             assertThat(message).isEqualTo(customMessage)
@@ -287,11 +279,10 @@
             val isThrottled by collectLastValue(underTest.isThrottled)
             val throttling by collectLastValue(underTest.throttling)
             val message by collectLastValue(underTest.message)
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
-            underTest.showOrUnlockDevice(SceneTestUtils.CONTAINER_1)
+            underTest.showOrUnlockDevice()
             runCurrent()
             assertThat(currentScene?.key).isEqualTo(SceneKey.Bouncer)
             assertThat(isThrottled).isFalse()
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 b1533fe..1f089ca 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
@@ -72,18 +72,14 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -96,18 +92,14 @@
     @Test
     fun onPasswordInputChanged() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -122,16 +114,12 @@
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("password")
@@ -144,18 +132,14 @@
     @Test
     fun onAuthenticateKeyPressed_whenWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
@@ -170,18 +154,14 @@
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
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 f69cbb8..af54989 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
@@ -75,8 +75,7 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -84,10 +83,7 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -101,8 +97,7 @@
     @Test
     fun onDragStart() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -110,10 +105,7 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -129,18 +121,14 @@
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -180,8 +168,7 @@
     @Test
     fun onDragEnd_whenWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -189,10 +176,7 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -216,8 +200,7 @@
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -225,10 +208,7 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
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 8edc6cf..c12ed03 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
@@ -75,15 +75,11 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -96,16 +92,12 @@
     @Test
     fun onPinButtonClicked() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -120,16 +112,12 @@
     @Test
     fun onBackspaceButtonClicked() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -146,15 +134,11 @@
     @Test
     fun onPinEdit() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
 
@@ -172,16 +156,12 @@
     @Test
     fun onBackspaceButtonLongPressed() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -200,14 +180,10 @@
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -222,16 +198,12 @@
     @Test
     fun onAuthenticateButtonClicked_whenWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -250,16 +222,12 @@
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -286,15 +254,11 @@
     @Test
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -307,17 +271,13 @@
     @Test
     fun onAutoConfirm_whenWrong() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             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.setCurrentScene(
-                SceneTestUtils.CONTAINER_1,
-                SceneModel(SceneKey.Bouncer)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer))
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
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..692d794 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
@@ -24,7 +24,6 @@
 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.plugins.ActivityStarter
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -36,7 +35,6 @@
 import org.mockito.Answers
 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 +42,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
 
@@ -102,14 +101,11 @@
                 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 +151,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())
 
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 71d2ec1..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
@@ -16,8 +16,6 @@
 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
 
@@ -65,7 +62,6 @@
         ActivityTestRule(
             /* activityFactory= */ SingleActivityFactory {
                 TestableControlsEditingActivity(
-                    featureFlags,
                     uiExecutor,
                     controller,
                     userTracker,
@@ -81,8 +77,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     @Test
@@ -101,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)
@@ -120,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()
@@ -129,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()
@@ -138,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)
 
@@ -177,7 +159,6 @@
         )
 
     class TestableControlsEditingActivity(
-        featureFlags: FakeFeatureFlags,
         executor: FakeExecutor,
         controller: ControlsControllerImpl,
         userTracker: UserTracker,
@@ -186,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 f11c296..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
@@ -17,14 +17,11 @@
 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,9 +77,6 @@
     @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
@@ -93,10 +85,8 @@
         ActivityTestRule(
             /* activityFactory= */ SingleActivityFactory {
                 TestableControlsFavoritingActivity(
-                    featureFlags,
                     executor,
                     controller,
-                    listingController,
                     userTracker,
                     mockDispatcher,
                     latch
@@ -109,7 +99,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     // b/259549854 to root-cause and fix
@@ -130,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()
@@ -149,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(
@@ -224,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/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 1f66e5b..28328c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -316,7 +316,7 @@
         TestableLooper.get(this).processAllMessages();
 
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
-        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onWakeAndUnlocking(false);
         mViewMediator.onStartedWakingUp(OFF_BECAUSE_OF_USER, false);
         TestableLooper.get(this).processAllMessages();
 
@@ -378,6 +378,8 @@
         mViewMediator.onSystemReady();
         TestableLooper.get(this).processAllMessages();
 
+        when(mPowerManager.isInteractive()).thenReturn(true);
+
         // Given device is dreaming
         when(mUpdateMonitor.isDreaming()).thenReturn(true);
 
@@ -717,14 +719,14 @@
 
     @Test
     public void testWakeAndUnlocking() {
-        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onWakeAndUnlocking(false);
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
     }
 
     @Test
     public void testWakeAndUnlockingOverDream() {
         // Send signal to wake
-        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onWakeAndUnlocking(true);
 
         // Ensure not woken up yet
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -753,7 +755,7 @@
     @Test
     public void testWakeAndUnlockingOverDream_signalAuthenticateIfStillShowing() {
         // Send signal to wake
-        mViewMediator.onWakeAndUnlocking();
+        mViewMediator.onWakeAndUnlocking(true);
 
         // Ensure not woken up yet
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
@@ -783,6 +785,35 @@
     }
 
     @Test
+    public void testWakeAndUnlockingOverNonInteractiveDream_noWakeByKeyguardViewMediator() {
+        // Send signal to wake
+        mViewMediator.onWakeAndUnlocking(false);
+
+        // Ensure not woken up yet
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+
+        // Verify keyguard told of authentication
+        verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean());
+        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();
+
+        // Verify not woken up.
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+    }
+
+    @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testDoKeyguardWhileInteractive_resets() {
         mViewMediator.setShowingLocked(true);
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 01a6c64..85ee0e4 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
@@ -541,10 +541,8 @@
         }
 
     @Test
-    fun authenticateDoesNotRunIfUserIsCurrentlySwitching() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth { fakeUserRepository.setUserSwitching(true) }
-        }
+    fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() =
+        testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } }
 
     @Test
     fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
@@ -840,7 +838,7 @@
 
     @Test
     fun detectDoesNotRunWhenUserSwitchingInProgress() =
-        testScope.runTest { testGatingCheckForDetect { fakeUserRepository.setUserSwitching(true) } }
+        testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } }
 
     @Test
     fun detectDoesNotRunWhenKeyguardGoingAway() =
@@ -1130,7 +1128,7 @@
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
         biometricSettingsRepository.setFaceEnrolled(true)
         biometricSettingsRepository.setIsFaceAuthEnabled(true)
-        fakeUserRepository.setUserSwitching(false)
+        underTest.resumeFaceAuth()
         trustRepository.setCurrentUserTrusted(false)
         keyguardRepository.setKeyguardGoingAway(false)
         keyguardRepository.setWakefulnessModel(
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 ba7d349..5e3376a 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
@@ -49,6 +49,7 @@
 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
@@ -85,13 +86,14 @@
     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,
@@ -107,6 +109,7 @@
                 dreamOverlayCallbackController,
                 mainDispatcher,
                 testScope.backgroundScope,
+                systemClock,
             )
     }
 
@@ -167,11 +170,15 @@
     @Test
     fun dozeTimeTick() =
         testScope.runTest {
-            var dozeTimeTickValue = collectLastValue(underTest.dozeTimeTick)
-            underTest.dozeTimeTick()
-            runCurrent()
+            val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
+            assertThat(lastDozeTimeTick).isEqualTo(0L)
 
-            assertThat(dozeTimeTickValue()).isNull()
+            // 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
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/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 8636dd8..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
@@ -48,6 +48,7 @@
 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
@@ -74,6 +75,7 @@
     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
 
@@ -98,6 +100,7 @@
                 .keyguardTransitionInteractor
 
         fakeDeviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+        fakeUserRepository = FakeUserRepository()
         underTest =
             SystemUIKeyguardFaceAuthInteractor(
                 mContext,
@@ -131,7 +134,8 @@
                 featureFlags,
                 FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
                 keyguardUpdateMonitor,
-                fakeDeviceEntryFingerprintAuthRepository
+                fakeDeviceEntryFingerprintAuthRepository,
+                fakeUserRepository,
             )
     }
 
@@ -212,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()
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 d01a46e..daf5ce6 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
@@ -89,6 +89,8 @@
         FromAlternateBouncerTransitionInteractor
     private lateinit var fromPrimaryBouncerTransitionInteractor:
         FromPrimaryBouncerTransitionInteractor
+    private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
+        FromDreamingLockscreenHostedTransitionInteractor
 
     @Before
     fun setUp() {
@@ -140,6 +142,15 @@
                 )
                 .apply { start() }
 
+        fromDreamingLockscreenHostedTransitionInteractor =
+            FromDreamingLockscreenHostedTransitionInteractor(
+                    scope = testScope,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    transitionRepository = transitionRepository,
+                    transitionInteractor = transitionInteractor,
+                )
+                .apply { start() }
+
         fromAodTransitionInteractor =
             FromAodTransitionInteractor(
                     scope = testScope,
@@ -299,6 +310,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
@@ -353,6 +396,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
@@ -533,6 +719,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
@@ -726,6 +944,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
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
index ca6a5b6..d825c2a 100644
--- 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
@@ -21,7 +21,6 @@
 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
@@ -91,7 +90,7 @@
     @Test
     fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -104,7 +103,7 @@
     @Test
     fun dismissLockScreen_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setUnlocked(true)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -117,7 +116,7 @@
     @Test
     fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -131,11 +130,11 @@
     fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen))
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isUnlocked).isFalse()
 
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
 
             assertThat(isUnlocked).isFalse()
         }
@@ -145,13 +144,13 @@
         testScope.runTest {
             val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
             runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
             runCurrent()
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
             assertThat(isUnlocked).isFalse()
 
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
 
             assertThat(isUnlocked).isFalse()
         }
@@ -159,10 +158,10 @@
     @Test
     fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
             runCurrent()
-            sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings))
             runCurrent()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
 
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
index b019a21..6efec99 100644
--- 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
@@ -38,7 +38,6 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 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
@@ -68,6 +67,7 @@
     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
@@ -79,35 +79,32 @@
         MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         configRepository = FakeConfigurationRepository()
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
-        shadeRepository = FakeShadeRepository()
-        fakeCommandQueue = FakeCommandQueue()
         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,
-                FakeSystemClock(),
+                keyguardInteractor
             )
 
         underTest =
             UdfpsKeyguardInteractor(
                 configRepository,
                 burnInInteractor,
-                KeyguardInteractor(
-                    keyguardRepository,
-                    fakeCommandQueue,
-                    featureFlags,
-                    bouncerRepository,
-                    configRepository,
-                ),
+                keyguardInteractor,
                 shadeRepository,
                 dialogManager,
             )
@@ -215,7 +212,7 @@
 
     private fun setAwake() {
         keyguardRepository.setDozeAmount(0f)
-        burnInInteractor.dozeTimeTick()
+        keyguardRepository.dozeTimeTick()
 
         bouncerRepository.setAlternateVisible(false)
         keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
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 ba8e0f2..63ee240 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
@@ -22,9 +22,7 @@
 import com.android.systemui.authentication.shared.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
@@ -51,20 +49,15 @@
     private val underTest =
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            interactorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
+            interactor =
+                utils.lockScreenSceneInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    bouncerInteractor =
+                        utils.bouncerInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = CONTAINER_1
+                            sceneInteractor = sceneInteractor,
+                        ),
+                ),
         )
 
     @Test
@@ -116,7 +109,7 @@
     @Test
     fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
@@ -129,7 +122,7 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -142,7 +135,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
@@ -155,7 +148,7 @@
     @Test
     fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
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
index b985b3c..bd17de3 100644
--- 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
@@ -20,21 +20,19 @@
 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.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.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.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -58,9 +56,9 @@
     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 shadeRepository: FakeShadeRepository
+    private lateinit var keyguardInteractor: KeyguardInteractor
 
     @Mock private lateinit var dialogManager: SystemUIDialogManager
     @Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -70,17 +68,21 @@
         MockitoAnnotations.initMocks(this)
         overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding)
         testScope = TestScope()
-        configRepository = FakeConfigurationRepository()
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
-        fakeCommandQueue = FakeCommandQueue()
         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,
@@ -89,15 +91,9 @@
                     burnInHelper,
                     testScope.backgroundScope,
                     configRepository,
-                    FakeSystemClock(),
+                    keyguardInteractor,
                 ),
-                KeyguardInteractor(
-                    keyguardRepository,
-                    fakeCommandQueue,
-                    featureFlags,
-                    bouncerRepository,
-                    configRepository,
-                ),
+                keyguardInteractor,
                 shadeRepository,
                 dialogManager,
             )
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
index 0fbcec2..80ab418 100644
--- 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
@@ -30,12 +30,11 @@
 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.KeyguardTransitionInteractor
 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.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -90,14 +89,10 @@
                 testScope.backgroundScope,
             )
         val keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                fakeCommandQueue,
-                featureFlags,
-                bouncerRepository,
-                configRepository,
-            )
-
+            KeyguardInteractorFactory.create(
+                    featureFlags = featureFlags,
+                )
+                .keyguardInteractor
         underTest =
             FingerprintViewModel(
                 context,
@@ -109,7 +104,7 @@
                         burnInHelper,
                         testScope.backgroundScope,
                         configRepository,
-                        FakeSystemClock(),
+                        keyguardInteractor,
                     ),
                     keyguardInteractor,
                     shadeRepository,
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
index 41ae931..0456824 100644
--- 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
@@ -29,6 +29,7 @@
 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.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -39,7 +40,6 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.time.FakeSystemClock
 import com.android.wm.shell.animation.Interpolators
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
@@ -72,6 +72,7 @@
     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
@@ -81,23 +82,21 @@
         MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         transitionRepository = FakeKeyguardTransitionRepository()
-        configRepository = FakeConfigurationRepository()
-        keyguardRepository = FakeKeyguardRepository()
-        bouncerRepository = FakeKeyguardBouncerRepository()
         shadeRepository = FakeShadeRepository()
         featureFlags =
             FakeFeatureFlags().apply {
                 set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true)
                 set(Flags.FACE_AUTH_REFACTOR, false)
             }
-        val keyguardInteractor =
-            KeyguardInteractor(
-                keyguardRepository,
-                commandQueue = mock(),
-                featureFlags,
-                bouncerRepository,
-                configRepository,
+        KeyguardInteractorFactory.create(
+                featureFlags = featureFlags,
             )
+            .also {
+                keyguardInteractor = it.keyguardInteractor
+                keyguardRepository = it.repository
+                configRepository = it.configurationRepository
+                bouncerRepository = it.bouncerRepository
+            }
 
         underTest =
             UdfpsLockscreenViewModel(
@@ -115,7 +114,7 @@
                         burnInHelperWrapper = mock(),
                         testScope.backgroundScope,
                         configRepository,
-                        FakeSystemClock(),
+                        keyguardInteractor,
                     ),
                     keyguardInteractor,
                     shadeRepository,
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 708bb55..1931580 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
@@ -98,8 +98,6 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class MediaOutputControllerTest extends SysuiTestCase {
-
-    private static final String TEST_PACKAGE_NAME = "com.test.package.name";
     private static final String TEST_DEVICE_1_ID = "test_device_1_id";
     private static final String TEST_DEVICE_2_ID = "test_device_2_id";
     private static final String TEST_DEVICE_3_ID = "test_device_3_id";
@@ -167,6 +165,7 @@
     final Notification mNotification = mock(Notification.class);
 
     private Context mSpyContext;
+    private String mPackageName = null;
     private MediaOutputController mMediaOutputController;
     private LocalMediaManager mLocalMediaManager;
     private List<MediaController> mMediaControllers = new ArrayList<>();
@@ -177,12 +176,14 @@
 
     @Before
     public void setUp() {
+        mPackageName = mContext.getPackageName();
+
         MockitoAnnotations.initMocks(this);
         mContext.setMockPackageManager(mPackageManager);
         mSpyContext = spy(mContext);
         final UserHandle userHandle = mock(UserHandle.class);
         when(mUserTracker.getUserHandle()).thenReturn(userHandle);
-        when(mSessionMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mSessionMediaController.getPackageName()).thenReturn(mPackageName);
         when(mSessionMediaController.getPlaybackState()).thenReturn(mPlaybackState);
         mMediaControllers.add(mSessionMediaController);
         when(mMediaSessionManager.getActiveSessionsForUser(any(),
@@ -193,7 +194,7 @@
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
                 mCachedBluetoothDeviceManager);
 
-        mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
+        mMediaOutputController = new MediaOutputController(mSpyContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
@@ -231,7 +232,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(mNotification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         mNotification.extras = bundle;
         when(bundle.getParcelable(Notification.EXTRA_MEDIA_SESSION,
                 MediaSession.Token.class)).thenReturn(token);
@@ -339,8 +340,8 @@
     public void tryToLaunchMediaApplication_intentNotNull_startActivity() {
         when(mDialogLaunchAnimator.createActivityLaunchController(any(View.class))).thenReturn(
                 mController);
-        Intent intent = new Intent(TEST_PACKAGE_NAME);
-        doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(TEST_PACKAGE_NAME);
+        Intent intent = new Intent(mPackageName);
+        doReturn(intent).when(mPackageManager).getLaunchIntentForPackage(mPackageName);
         mMediaOutputController.start(mCallback);
 
         mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
@@ -355,7 +356,7 @@
                 mController);
         mMediaOutputController.start(mCallback);
         when(mLocalMediaManager.getLinkedItemComponentName()).thenReturn(
-                new ComponentName(TEST_PACKAGE_NAME, ""));
+                new ComponentName(mPackageName, ""));
 
         mMediaOutputController.tryToLaunchInAppRoutingIntent(TEST_DEVICE_1_ID, mDialogLaunchView);
 
@@ -891,7 +892,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getLargeIcon()).thenReturn(null);
 
@@ -910,7 +911,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getLargeIcon()).thenReturn(icon);
 
@@ -929,7 +930,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         when(notification.isMediaNotification()).thenReturn(false);
         when(notification.getLargeIcon()).thenReturn(icon);
 
@@ -947,7 +948,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getSmallIcon()).thenReturn(null);
 
@@ -966,7 +967,7 @@
         when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
         when(entry.getSbn()).thenReturn(sbn);
         when(sbn.getNotification()).thenReturn(notification);
-        when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(sbn.getPackageName()).thenReturn(mPackageName);
         when(notification.isMediaNotification()).thenReturn(true);
         when(notification.getSmallIcon()).thenReturn(icon);
 
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 ed7a59e..ee42a70 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
@@ -20,9 +20,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.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
@@ -48,26 +46,21 @@
 
     private val underTest =
         QuickSettingsSceneViewModel(
-            lockscreenSceneInteractorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
+            lockscreenSceneInteractor =
+                utils.lockScreenSceneInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    bouncerInteractor =
+                        utils.bouncerInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = CONTAINER_1
+                            sceneInteractor = sceneInteractor,
+                        ),
+                ),
         )
 
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -80,7 +73,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
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 9ce378d..826a6cc 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
@@ -41,7 +41,7 @@
     @Test
     fun allSceneKeys() {
         val underTest = utils.fakeSceneContainerRepository()
-        assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
+        assertThat(underTest.allSceneKeys())
             .isEqualTo(
                 listOf(
                     SceneKey.QuickSettings,
@@ -53,115 +53,58 @@
             )
     }
 
-    @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(SceneTestUtils.CONTAINER_1))
+        val currentScene by collectLastValue(underTest.currentScene)
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(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() {
         val underTest =
             utils.fakeSceneContainerRepository(
-                setOf(
-                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
-                    utils.fakeSceneContainerConfig(
-                        SceneTestUtils.CONTAINER_2,
-                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-                    ),
-                )
+                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
             )
-        underTest.setCurrentScene(SceneTestUtils.CONTAINER_2, SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun isVisible() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
-        val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
+        val isVisible by collectLastValue(underTest.isVisible)
         assertThat(isVisible).isTrue()
 
-        underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
+        underTest.setVisible(false)
         assertThat(isVisible).isFalse()
 
-        underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
+        underTest.setVisible(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)
-    }
-
     @Test
-    fun sceneTransitionProgress() = runTest {
+    fun transitionProgress() = runTest {
         val underTest = utils.fakeSceneContainerRepository()
-        val sceneTransitionProgress by
-            collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
+        val sceneTransitionProgress by collectLastValue(underTest.transitionProgress)
         assertThat(sceneTransitionProgress).isEqualTo(1f)
 
-        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.1f)
+        underTest.setSceneTransitionProgress(0.1f)
         assertThat(sceneTransitionProgress).isEqualTo(0.1f)
 
-        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.9f)
+        underTest.setSceneTransitionProgress(0.9f)
         assertThat(sceneTransitionProgress).isEqualTo(0.9f)
     }
 
-    @Test(expected = IllegalStateException::class)
-    fun sceneTransitionProgress_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.sceneTransitionProgress("nonExistingContainer")
-    }
-
     @Test
     fun setSceneTransition() = runTest {
-        val underTest =
-            utils.fakeSceneContainerRepository(
-                setOf(
-                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
-                    utils.fakeSceneContainerConfig(
-                        SceneTestUtils.CONTAINER_2,
-                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-                    ),
-                )
-            )
-        val sceneTransition by
-            collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_2))
+        val underTest = utils.fakeSceneContainerRepository()
+        val sceneTransition by collectLastValue(underTest.transitions)
         assertThat(sceneTransition).isNull()
 
-        underTest.setSceneTransition(
-            SceneTestUtils.CONTAINER_2,
-            SceneKey.Lockscreen,
-            SceneKey.QuickSettings
-        )
+        underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings)
         assertThat(sceneTransition)
             .isEqualTo(
                 SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
@@ -169,46 +112,20 @@
     }
 
     @Test(expected = IllegalStateException::class)
-    fun setSceneTransition_noSuchContainer_throws() {
-        val underTest = utils.fakeSceneContainerRepository()
-        underTest.setSceneTransition("nonExistingContainer", SceneKey.Lockscreen, SceneKey.Shade)
-    }
-
-    @Test(expected = IllegalStateException::class)
     fun setSceneTransition_noFromSceneInContainer_throws() {
         val underTest =
             utils.fakeSceneContainerRepository(
-                setOf(
-                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
-                    utils.fakeSceneContainerConfig(
-                        SceneTestUtils.CONTAINER_2,
-                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-                    ),
-                )
+                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
             )
-        underTest.setSceneTransition(
-            SceneTestUtils.CONTAINER_2,
-            SceneKey.Shade,
-            SceneKey.Lockscreen
-        )
+        underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
     }
 
     @Test(expected = IllegalStateException::class)
     fun setSceneTransition_noToSceneInContainer_throws() {
         val underTest =
             utils.fakeSceneContainerRepository(
-                setOf(
-                    utils.fakeSceneContainerConfig(SceneTestUtils.CONTAINER_1),
-                    utils.fakeSceneContainerConfig(
-                        SceneTestUtils.CONTAINER_2,
-                        listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
-                    ),
-                )
+                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
             )
-        underTest.setSceneTransition(
-            SceneTestUtils.CONTAINER_2,
-            SceneKey.Shade,
-            SceneKey.Lockscreen
-        )
+        underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
     }
 }
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 d2bbfa8..13a602d 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
@@ -41,48 +41,46 @@
 
     @Test
     fun allSceneKeys() {
-        assertThat(underTest.allSceneKeys(SceneTestUtils.CONTAINER_1))
-            .isEqualTo(utils.fakeSceneKeys())
+        assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
     }
 
     @Test
     fun currentScene() = runTest {
-        val currentScene by collectLastValue(underTest.currentScene(SceneTestUtils.CONTAINER_1))
+        val currentScene by collectLastValue(underTest.currentScene)
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
     }
 
     @Test
     fun sceneTransitionProgress() = runTest {
-        val progress by
-            collectLastValue(underTest.sceneTransitionProgress(SceneTestUtils.CONTAINER_1))
+        val progress by collectLastValue(underTest.transitionProgress)
         assertThat(progress).isEqualTo(1f)
 
-        underTest.setSceneTransitionProgress(SceneTestUtils.CONTAINER_1, 0.55f)
+        underTest.setSceneTransitionProgress(0.55f)
         assertThat(progress).isEqualTo(0.55f)
     }
 
     @Test
     fun isVisible() = runTest {
-        val isVisible by collectLastValue(underTest.isVisible(SceneTestUtils.CONTAINER_1))
+        val isVisible by collectLastValue(underTest.isVisible)
         assertThat(isVisible).isTrue()
 
-        underTest.setVisible(SceneTestUtils.CONTAINER_1, false)
+        underTest.setVisible(false)
         assertThat(isVisible).isFalse()
 
-        underTest.setVisible(SceneTestUtils.CONTAINER_1, true)
+        underTest.setVisible(true)
         assertThat(isVisible).isTrue()
     }
 
     @Test
     fun sceneTransitions() = runTest {
-        val transitions by collectLastValue(underTest.sceneTransitions(SceneTestUtils.CONTAINER_1))
+        val transitions by collectLastValue(underTest.transitions)
         assertThat(transitions).isNull()
 
-        val initialSceneKey = underTest.currentScene(SceneTestUtils.CONTAINER_1).value.key
-        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.Shade))
+        val initialSceneKey = underTest.currentScene.value.key
+        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
         assertThat(transitions)
             .isEqualTo(
                 SceneTransitionModel(
@@ -91,7 +89,7 @@
                 )
             )
 
-        underTest.setCurrentScene(SceneTestUtils.CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+        underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings))
         assertThat(transitions)
             .isEqualTo(
                 SceneTransitionModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 6f6c5a5..b6bd31f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -28,7 +28,6 @@
 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.shared.model.SceneContainerNames
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.util.mockito.mock
@@ -47,7 +46,7 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() {
+class SceneContainerStartableTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
@@ -66,7 +65,7 @@
     private val sysUiState: SysUiState = mock()
 
     private val underTest =
-        SystemUiDefaultSceneContainerStartable(
+        SceneContainerStartable(
             applicationScope = testScope.backgroundScope,
             sceneInteractor = sceneInteractor,
             authenticationInteractor = authenticationInteractor,
@@ -84,14 +83,8 @@
     @Test
     fun hydrateVisibility_featureEnabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
-            val isVisible by
-                collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
             prepareState(
                 isFeatureEnabled = true,
                 isDeviceUnlocked = true,
@@ -104,24 +97,15 @@
 
             assertThat(isVisible).isFalse()
 
-            sceneInteractor.setCurrentScene(
-                SceneContainerNames.SYSTEM_UI_DEFAULT,
-                SceneModel(SceneKey.Shade)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
             assertThat(isVisible).isTrue()
         }
 
     @Test
     fun hydrateVisibility_featureDisabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
-            val isVisible by
-                collectLastValue(sceneInteractor.isVisible(SceneContainerNames.SYSTEM_UI_DEFAULT))
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val isVisible by collectLastValue(sceneInteractor.isVisible)
             prepareState(
                 isFeatureEnabled = false,
                 isDeviceUnlocked = true,
@@ -133,28 +117,17 @@
             underTest.start()
             assertThat(isVisible).isTrue()
 
-            sceneInteractor.setCurrentScene(
-                SceneContainerNames.SYSTEM_UI_DEFAULT,
-                SceneModel(SceneKey.Gone)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone))
             assertThat(isVisible).isTrue()
 
-            sceneInteractor.setCurrentScene(
-                SceneContainerNames.SYSTEM_UI_DEFAULT,
-                SceneModel(SceneKey.Shade)
-            )
+            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade))
             assertThat(isVisible).isTrue()
         }
 
     @Test
     fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isDeviceUnlocked = true,
@@ -171,12 +144,7 @@
     @Test
     fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = false,
                 isDeviceUnlocked = false,
@@ -193,12 +161,7 @@
     @Test
     fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isDeviceUnlocked = false,
@@ -215,12 +178,7 @@
     @Test
     fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = false,
                 isDeviceUnlocked = false,
@@ -237,12 +195,7 @@
     @Test
     fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isBypassEnabled = true,
@@ -259,12 +212,7 @@
     @Test
     fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isBypassEnabled = false,
@@ -281,12 +229,7 @@
     @Test
     fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = false,
                 isBypassEnabled = true,
@@ -303,12 +246,7 @@
     @Test
     fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isDeviceUnlocked = true,
@@ -325,12 +263,7 @@
     @Test
     fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = false,
                 isDeviceUnlocked = true,
@@ -347,12 +280,7 @@
     @Test
     fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = true,
                 isDeviceUnlocked = false,
@@ -369,12 +297,7 @@
     @Test
     fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
         testScope.runTest {
-            val currentSceneKey by
-                collectLastValue(
-                    sceneInteractor.currentScene(SceneContainerNames.SYSTEM_UI_DEFAULT).map {
-                        it.key
-                    }
-                )
+            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
             prepareState(
                 isFeatureEnabled = false,
                 isDeviceUnlocked = false,
@@ -403,10 +326,7 @@
                     SceneKey.QuickSettings,
                 )
                 .forEachIndexed { index, sceneKey ->
-                    sceneInteractor.setCurrentScene(
-                        SceneContainerNames.SYSTEM_UI_DEFAULT,
-                        SceneModel(sceneKey),
-                    )
+                    sceneInteractor.setCurrentScene(SceneModel(sceneKey))
                     runCurrent()
 
                     verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
@@ -422,9 +342,7 @@
         featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
         authenticationRepository.setUnlocked(isDeviceUnlocked)
         keyguardRepository.setBypassEnabled(isBypassEnabled)
-        initialSceneKey?.let {
-            sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it))
-        }
+        initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it)) }
     }
 
     companion object {
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 63ea918c..0ab98ad 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
@@ -45,7 +45,6 @@
     private val underTest =
         SceneContainerViewModel(
             interactor = interactor,
-            containerName = SceneTestUtils.CONTAINER_1,
         )
 
     @Test
@@ -53,10 +52,10 @@
         val isVisible by collectLastValue(underTest.isVisible)
         assertThat(isVisible).isTrue()
 
-        interactor.setVisible(SceneTestUtils.CONTAINER_1, false)
+        interactor.setVisible(false)
         assertThat(isVisible).isFalse()
 
-        interactor.setVisible(SceneTestUtils.CONTAINER_1, true)
+        interactor.setVisible(true)
         assertThat(isVisible).isTrue()
     }
 
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 47ca49d0..9bcc8aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -99,6 +99,7 @@
 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;
@@ -297,7 +298,8 @@
     @Mock protected LockscreenToOccludedTransitionViewModel
             mLockscreenToOccludedTransitionViewModel;
     @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
-
+    @Mock protected GoneToDreamingLockscreenHostedTransitionViewModel
+            mGoneToDreamingLockscreenHostedTransitionViewModel;
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -371,6 +373,7 @@
                 mKeyguardLogger,
                 mFeatureFlags,
                 mInteractionJankMonitor,
+                mKeyguardInteractor,
                 mDumpManager));
 
         when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
@@ -477,6 +480,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());
@@ -612,6 +629,7 @@
                 mOccludedToLockscreenTransitionViewModel,
                 mLockscreenToDreamingTransitionViewModel,
                 mGoneToDreamingTransitionViewModel,
+                mGoneToDreamingLockscreenHostedTransitionViewModel,
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
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 6e9fba6..8739b28 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
@@ -20,7 +20,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.shared.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.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
@@ -48,20 +47,15 @@
     private val underTest =
         ShadeSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            lockscreenSceneInteractorFactory =
-                object : LockscreenSceneInteractor.Factory {
-                    override fun create(containerName: String): LockscreenSceneInteractor {
-                        return utils.lockScreenSceneInteractor(
+            lockscreenSceneInteractor =
+                utils.lockScreenSceneInteractor(
+                    authenticationInteractor = authenticationInteractor,
+                    bouncerInteractor =
+                        utils.bouncerInteractor(
                             authenticationInteractor = authenticationInteractor,
-                            bouncerInteractor =
-                                utils.bouncerInteractor(
-                                    authenticationInteractor = authenticationInteractor,
-                                    sceneInteractor = sceneInteractor,
-                                ),
-                        )
-                    }
-                },
-            containerName = SceneTestUtils.CONTAINER_1
+                            sceneInteractor = sceneInteractor,
+                        ),
+                ),
         )
 
     @Test
@@ -87,8 +81,7 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -101,8 +94,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by
-                collectLastValue(sceneInteractor.currentScene(SceneTestUtils.CONTAINER_1))
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
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
index 0b2da8b..0cfca61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -24,6 +24,7 @@
 import android.view.Gravity
 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.flags.FakeFeatureFlags
@@ -38,6 +39,7 @@
 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
@@ -50,6 +52,7 @@
 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
 
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 1dc8453..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
@@ -789,6 +789,50 @@
         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/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 91aa138..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
@@ -199,7 +199,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
-        verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        verify(mKeyguardViewMediator).onWakeAndUnlocking(false);
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
     }
@@ -217,7 +217,7 @@
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
                 BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
-        verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        verify(mKeyguardViewMediator).onWakeAndUnlocking(false);
         assertThat(mBiometricUnlockController.getMode())
                 .isEqualTo(MODE_WAKE_AND_UNLOCK);
     }
@@ -671,8 +671,9 @@
         when(mWakefulnessLifecycle.getLastWakeReason())
                 .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
         givenDreamingLocked();
+        when(mPowerManager.isInteractive()).thenReturn(true);
         mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, true);
-        verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        verify(mKeyguardViewMediator).onWakeAndUnlocking(true);
         // Ensure that the power hasn't been told to wake up yet.
         verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
     }
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/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/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/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/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 0663004..462fd0a 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;
@@ -105,6 +104,8 @@
     private UserTracker mUserTracker;
     @Mock
     private DumpManager mDumpManager;
+    @Mock
+    private Handler mHandler;
 
 
     @Before
@@ -117,6 +118,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,8 +129,8 @@
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext,
                 mBroadcastDispatcher, mRingerModeTracker, mThreadFactory, mAudioManager,
                 mNotificationManager, mVibrator, mIAudioService, mAccessibilityManager,
-                mPackageManager, mWakefullnessLifcycle, mCaptioningManager, mKeyguardManager,
-                mActivityManager, mUserTracker, mDumpManager, mCallback);
+                mPackageManager, mWakefullnessLifcycle, mKeyguardManager,
+                mActivityManager, mUserTracker, mDumpManager, mHandler, mCallback);
         mVolumeController.setEnableDialogs(true, true);
     }
 
@@ -219,6 +221,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,16 +241,16 @@
                 AccessibilityManager accessibilityManager,
                 PackageManager packageManager,
                 WakefulnessLifecycle wakefulnessLifecycle,
-                CaptioningManager captioningManager,
                 KeyguardManager keyguardManager,
                 ActivityManager activityManager,
                 UserTracker userTracker,
                 DumpManager dumpManager,
+                Handler mainHandler,
                 C callback) {
             super(context, broadcastDispatcher, ringerModeTracker, theadFactory, audioManager,
                     notificationManager, optionalVibrator, iAudioService, accessibilityManager,
-                    packageManager, wakefulnessLifecycle, captioningManager, keyguardManager,
-                    activityManager, userTracker, dumpManager);
+                    packageManager, wakefulnessLifecycle, keyguardManager,
+                    activityManager, userTracker, dumpManager, mainHandler);
             mCallbacks = callback;
 
             ArgumentCaptor<WakefulnessLifecycle.Observer> observerCaptor =
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 2f228a8..e10a80c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -158,7 +158,6 @@
 
 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;
@@ -174,7 +173,6 @@
 import java.util.List;
 import java.util.Optional;
 
-@Ignore("b/292153259")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -280,8 +278,6 @@
     @Mock
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
-    private ShellTaskOrganizer mShellTaskOrganizer;
-    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private ScreenOffAnimationController mScreenOffAnimationController;
@@ -298,6 +294,7 @@
     @Mock
     private Icon mAppBubbleIcon;
 
+    private ShellTaskOrganizer mShellTaskOrganizer;
     private TaskViewTransitions mTaskViewTransitions;
 
     private TestableBubblePositioner mPositioner;
@@ -379,7 +376,13 @@
                         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,
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..026372f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/androidx/core/animation/AndroidXAnimatorIsolationRule.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 androidx.core.animation
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class AndroidXAnimatorIsolationRule : TestRule {
+
+    private class TestAnimationHandler : AnimationHandler(null) {
+        override fun addAnimationFrameCallback(callback: AnimationFrameCallback?) = doFail()
+        override fun removeCallback(callback: AnimationFrameCallback?) = doFail()
+        override fun onAnimationFrame(frameTime: Long) = doFail()
+        override fun setFrameDelay(frameDelay: Long) = doFail()
+        override fun getFrameDelay(): Long = doFail()
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                AnimationHandler.setTestHandler(testHandler)
+                try {
+                    base.evaluate()
+                } finally {
+                    AnimationHandler.setTestHandler(null)
+                }
+            }
+        }
+    }
+
+    companion object {
+        private val testHandler = TestAnimationHandler()
+        private fun doFail(): Nothing =
+            error(
+                "Test's animations are not isolated! " +
+                    "Did you forget to add an AnimatorTestRule to your test class?"
+            )
+    }
+}
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 de177168..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());
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 f4c2db1..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
@@ -61,6 +61,19 @@
         _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
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 e6894d7..15ce055 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
@@ -30,7 +30,6 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -60,7 +59,7 @@
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: StateFlow<Boolean> = _isDozing
 
-    private val _dozeTimeTick = MutableSharedFlow<Unit>()
+    private val _dozeTimeTick = MutableStateFlow<Long>(0L)
     override val dozeTimeTick = _dozeTimeTick
 
     private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
@@ -174,7 +173,11 @@
     }
 
     override fun dozeTimeTick() {
-        _dozeTimeTick.tryEmit(Unit)
+        _dozeTimeTick.value = _dozeTimeTick.value + 1
+    }
+
+    fun dozeTimeTick(millis: Long) {
+        _dozeTimeTick.value = millis
     }
 
     override fun setLastDozeTapToWakePosition(position: Point) {
@@ -237,6 +240,10 @@
         _isBypassEnabled = isEnabled
     }
 
+    fun setScreenModel(screenModel: ScreenModel) {
+        _screenModel.value = screenModel
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
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 f39982f..26a75d0 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
@@ -41,7 +41,6 @@
 import com.android.systemui.scene.shared.model.RemoteUserInput
 import com.android.systemui.scene.shared.model.RemoteUserInputAction
 import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneContainerNames
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.UserRepository
@@ -96,13 +95,9 @@
     private val context = test.context
 
     fun fakeSceneContainerRepository(
-        containerConfigurations: Set<SceneContainerConfig> =
-            setOf(
-                fakeSceneContainerConfig(CONTAINER_1),
-                fakeSceneContainerConfig(CONTAINER_2),
-            )
+        containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
     ): SceneContainerRepository {
-        return SceneContainerRepository(containerConfigurations.associateBy { it.name })
+        return SceneContainerRepository(containerConfig)
     }
 
     fun fakeSceneKeys(): List<SceneKey> {
@@ -116,11 +111,9 @@
     }
 
     fun fakeSceneContainerConfig(
-        name: String,
         sceneKeys: List<SceneKey> = fakeSceneKeys(),
     ): SceneContainerConfig {
         return SceneContainerConfig(
-            name = name,
             sceneKeys = sceneKeys,
             initialSceneKey = SceneKey.Lockscreen,
         )
@@ -174,7 +167,6 @@
             authenticationInteractor = authenticationInteractor,
             sceneInteractor = sceneInteractor,
             featureFlags = featureFlags,
-            containerName = CONTAINER_1,
         )
     }
 
@@ -184,14 +176,8 @@
         return BouncerViewModel(
             applicationContext = context,
             applicationScope = applicationScope(),
-            interactorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
+            interactor = bouncerInteractor,
             featureFlags = featureFlags,
-            containerName = CONTAINER_1,
         )
     }
 
@@ -202,13 +188,7 @@
         return LockscreenSceneInteractor(
             applicationScope = applicationScope(),
             authenticationInteractor = authenticationInteractor,
-            bouncerInteractorFactory =
-                object : BouncerInteractor.Factory {
-                    override fun create(containerName: String): BouncerInteractor {
-                        return bouncerInteractor
-                    }
-                },
-            containerName = CONTAINER_1,
+            bouncerInteractor = bouncerInteractor,
         )
     }
 
@@ -217,9 +197,6 @@
     }
 
     companion object {
-        const val CONTAINER_1 = SceneContainerNames.SYSTEM_UI_DEFAULT
-        const val CONTAINER_2 = "container2"
-
         val REMOTE_INPUT_DOWN_GESTURE =
             listOf(
                 RemoteUserInput(10f, 10f, RemoteUserInputAction.DOWN),
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 777c7c8..9b9593b 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1344,6 +1344,8 @@
                 Slog.e(LOG_TAG, "Unable to find a valid pointer for touch exploration.");
                 return;
             }
+            // Send hover exit if we haven't closed a previous touch exploration event stream.
+            sendHoverExitAndTouchExplorationGestureEndIfNeeded(pointerId);
             final int pointerIdBits = (1 << pointerId);
             final int policyFlags = mState.getLastReceivedPolicyFlags();
             mSendHoverEnterAndMoveDelayed.setPointerIdBits(pointerIdBits);
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 d33d224..805f6e3 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -110,6 +110,7 @@
     private boolean mAlwaysOnMagnificationEnabled = false;
     private final DisplayManagerInternal mDisplayManagerInternal;
 
+    private final MagnificationThumbnailFeatureFlag mMagnificationThumbnailFeatureFlag;
     @NonNull private final Supplier<MagnificationThumbnail> mThumbnailSupplier;
 
     /**
@@ -689,6 +690,13 @@
             }
         }
 
+        void onThumbnailFeatureFlagChanged() {
+            synchronized (mLock) {
+                destroyThumbnail();
+                createThumbnailIfSupported();
+            }
+        }
+
         /**
          * Updates the current magnification spec.
          *
@@ -849,19 +857,43 @@
         addInfoChangedCallback(magnificationInfoChangedCallback);
         mScaleProvider = scaleProvider;
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
+        mMagnificationThumbnailFeatureFlag = new MagnificationThumbnailFeatureFlag();
+        mMagnificationThumbnailFeatureFlag.addOnChangedListener(
+                backgroundExecutor, this::onMagnificationThumbnailFeatureFlagChanged);
         if (thumbnailSupplier != null) {
             mThumbnailSupplier = thumbnailSupplier;
         } else {
             mThumbnailSupplier = () -> {
-                return new MagnificationThumbnail(
-                        ctx.getContext(),
-                        ctx.getContext().getSystemService(WindowManager.class),
-                        new Handler(ctx.getContext().getMainLooper())
-                );
+                if (mMagnificationThumbnailFeatureFlag.isFeatureFlagEnabled()) {
+                    return new MagnificationThumbnail(
+                            ctx.getContext(),
+                            ctx.getContext().getSystemService(WindowManager.class),
+                            new Handler(ctx.getContext().getMainLooper())
+                    );
+                }
+                return null;
             };
         }
     }
 
+    private void onMagnificationThumbnailFeatureFlagChanged() {
+        synchronized (mLock) {
+            for (int i = 0; i < mDisplays.size(); i++) {
+                onMagnificationThumbnailFeatureFlagChanged(mDisplays.keyAt(i));
+            }
+        }
+    }
+
+    private void onMagnificationThumbnailFeatureFlagChanged(int displayId) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            display.onThumbnailFeatureFlagChanged();
+        }
+    }
+
     /**
      * Start tracking the magnification region for services that control magnification and the
      * magnification gesture handler.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
new file mode 100644
index 0000000..519f31b
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnailFeatureFlag.java
@@ -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.server.accessibility.magnification;
+
+import android.provider.DeviceConfig;
+
+/**
+ * Encapsulates the feature flags for magnification thumbnail. {@see DeviceConfig}
+ *
+ * @hide
+ */
+public class MagnificationThumbnailFeatureFlag extends MagnificationFeatureFlagBase {
+
+    private static final String NAMESPACE = DeviceConfig.NAMESPACE_ACCESSIBILITY;
+    private static final String FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL =
+            "enable_magnifier_thumbnail";
+
+    @Override
+    String getNamespace() {
+        return NAMESPACE;
+    }
+
+    @Override
+    String getFeatureName() {
+        return FEATURE_NAME_ENABLE_MAGNIFIER_THUMBNAIL;
+    }
+
+    @Override
+    boolean getDefaultValue() {
+        return false;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index be52b1c..82d4d60 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
 import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
+import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
@@ -640,29 +641,44 @@
         }
 
         @Override
+        @EnforcePermission(USE_COMPANION_TRANSPORTS)
         public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+            addOnTransportsChangedListener_enforcePermission();
+
             mTransportManager.addListener(listener);
         }
 
         @Override
+        @EnforcePermission(USE_COMPANION_TRANSPORTS)
         public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+            removeOnTransportsChangedListener_enforcePermission();
+
             mTransportManager.removeListener(listener);
         }
 
         @Override
+        @EnforcePermission(USE_COMPANION_TRANSPORTS)
         public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+            sendMessage_enforcePermission();
+
             mTransportManager.sendMessage(messageType, data, associationIds);
         }
 
         @Override
+        @EnforcePermission(USE_COMPANION_TRANSPORTS)
         public void addOnMessageReceivedListener(int messageType,
                 IOnMessageReceivedListener listener) {
+            addOnMessageReceivedListener_enforcePermission();
+
             mTransportManager.addListener(messageType, listener);
         }
 
         @Override
+        @EnforcePermission(USE_COMPANION_TRANSPORTS)
         public void removeOnMessageReceivedListener(int messageType,
                 IOnMessageReceivedListener listener) {
+            removeOnMessageReceivedListener_enforcePermission();
+
             mTransportManager.removeListener(messageType, listener);
         }
 
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index c79466f..94cede8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,6 +16,8 @@
 
 package com.android.server.companion;
 
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
 import android.companion.Telecom;
@@ -30,7 +32,6 @@
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
 import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.companion.transport.Transport;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -135,7 +136,7 @@
                 case "send-context-sync-empty-message": {
                     associationId = getNextIntArgRequired();
                     mTransportManager.createEmulatedTransport(associationId)
-                            .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+                            .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
                                     /* sequence= */ 0,
                                     CrossDeviceSyncController.createEmptyMessage());
                     break;
@@ -147,7 +148,7 @@
                     String address = getNextArgRequired();
                     String facilitator = getNextArgRequired();
                     mTransportManager.createEmulatedTransport(associationId)
-                            .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+                            .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
                                     /* sequence= */ 0,
                                     CrossDeviceSyncController.createCallCreateMessage(callId,
                                             address, facilitator));
@@ -159,7 +160,7 @@
                     String callId = getNextArgRequired();
                     int control = getNextIntArgRequired();
                     mTransportManager.createEmulatedTransport(associationId)
-                            .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+                            .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
                                     /* sequence= */ 0,
                                     CrossDeviceSyncController.createCallControlMessage(callId,
                                             control));
@@ -184,7 +185,7 @@
                     }
                     pos.end(telecomToken);
                     mTransportManager.createEmulatedTransport(associationId)
-                            .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+                            .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
                                     /* sequence= */ 0, pos.getBytes());
                     break;
                 }
@@ -246,7 +247,7 @@
                     pos.end(callsToken);
                     pos.end(telecomToken);
                     mTransportManager.createEmulatedTransport(associationId)
-                            .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC,
+                            .processMessage(MESSAGE_REQUEST_CONTEXT_SYNC,
                                     /* sequence= */ 0, pos.getBytes());
                     break;
                 }
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 7af4957..13f41ed 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -19,10 +19,10 @@
 import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
 import static android.content.ComponentName.createRelative;
 
 import static com.android.server.companion.Utils.prepareForIpc;
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index 9bd5af9..ad1eff8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -16,7 +16,7 @@
 
 package com.android.server.companion.datatransfer.contextsync;
 
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
 
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 41867f9..f648f09 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -16,7 +16,7 @@
 
 package com.android.server.companion.transport;
 
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 5af3b98..32d4061 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -16,6 +16,11 @@
 
 package com.android.server.companion.transport;
 
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_REMOTE_AUTHENTICATION;
+
 import android.annotation.NonNull;
 import android.companion.IOnMessageReceivedListener;
 import android.content.Context;
@@ -45,10 +50,6 @@
     protected static final String TAG = "CDM_CompanionTransport";
     protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
-    static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-    public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
-    public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
-
     static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
     static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
 
@@ -181,7 +182,8 @@
                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
                 break;
             }
-            case MESSAGE_REQUEST_CONTEXT_SYNC: {
+            case MESSAGE_REQUEST_CONTEXT_SYNC:
+            case MESSAGE_REQUEST_REMOTE_AUTHENTICATION: {
                 callback(message, data);
                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
                 break;
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index f19f7f2..1741593 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -19,12 +19,10 @@
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
@@ -40,8 +38,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.MutableBoolean;
 import android.util.Slog;
@@ -113,19 +109,6 @@
      */
     private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
 
-    /** Action for starting emergency alerts on Wear OS. */
-    private static final String WEAR_LAUNCH_EMERGENCY_ACTION =
-            "com.android.systemui.action.LAUNCH_EMERGENCY";
-
-    /** Action for starting emergency alerts in retail mode on Wear OS. */
-    private static final String WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION =
-            "com.android.systemui.action.LAUNCH_EMERGENCY_RETAIL";
-
-    /**
-     * Boolean extra for distinguishing intents coming from power button gesture.
-     */
-    private static final String EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE = "launch_emergency_via_gesture";
-
     /** The listener that receives the gesture event. */
     private final GestureEventListener mGestureListener = new GestureEventListener();
     private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
@@ -198,7 +181,6 @@
     private final UiEventLogger mUiEventLogger;
 
     private boolean mHasFeatureWatch;
-    private long mVibrateMilliSecondsForPanicGesture;
 
     @VisibleForTesting
     public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@@ -268,13 +250,6 @@
 
             mHasFeatureWatch =
                     mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-            mVibrateMilliSecondsForPanicGesture =
-                    resources.getInteger(
-                            com.android
-                                    .internal
-                                    .R
-                                    .integer
-                                    .config_mashPressVibrateTimeOnPowerButton);
         }
     }
 
@@ -714,11 +689,6 @@
                         userSetupComplete));
             }
 
-            if (mHasFeatureWatch) {
-                onEmergencyGestureDetectedOnWatch();
-                return true;
-            }
-
             StatusBarManagerInternal service = LocalServices.getService(
                     StatusBarManagerInternal.class);
             service.onEmergencyActionLaunchGestureDetected();
@@ -728,37 +698,6 @@
         }
     }
 
-    private void onEmergencyGestureDetectedOnWatch() {
-        Intent emergencyIntent =
-                new Intent(
-                        isInRetailMode()
-                                ? WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION
-                                : WEAR_LAUNCH_EMERGENCY_ACTION);
-        PackageManager pm = mContext.getPackageManager();
-        ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
-        if (resolveInfo == null) {
-            Slog.w(TAG, "Couldn't find an app to process the emergency intent "
-                    + emergencyIntent.getAction());
-            return;
-        }
-
-        Vibrator vibrator = mContext.getSystemService(Vibrator.class);
-        vibrator.vibrate(VibrationEffect.createOneShot(mVibrateMilliSecondsForPanicGesture,
-                VibrationEffect.DEFAULT_AMPLITUDE));
-
-        emergencyIntent.setComponent(
-                new ComponentName(
-                        resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
-        emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        emergencyIntent.putExtra(EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE, true);
-        mContext.startActivityAsUser(emergencyIntent, new UserHandle(mUserId));
-    }
-
-    private boolean isInRetailMode() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.DEVICE_DEMO_MODE, 0) == 1;
-    }
-
     private boolean isUserSetupComplete() {
         return Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dc83125..383bb25 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6434,6 +6434,7 @@
         }
         updateServiceConnectionActivitiesLocked(psr);
         psr.removeAllConnections();
+        psr.removeAllSdkSandboxConnections();
 
         psr.mAllowlistManager = false;
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index bef53c7..faf1900a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -22,6 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean;
 
 import android.annotation.NonNull;
 import android.app.ActivityThread;
@@ -153,6 +154,11 @@
     static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
     static final String KEY_USE_MODERN_TRIM = "use_modern_trim";
 
+    /**
+     * Whether or not to enable the new oom adjuster implementation.
+     */
+    static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj";
+
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -216,6 +222,11 @@
     private static final boolean DEFAULT_USE_MODERN_TRIM = true;
 
     /**
+     * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
+     */
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+
+    /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
     private static final int
@@ -1051,6 +1062,9 @@
     /** @see #KEY_USE_MODERN_TRIM */
     public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM;
 
+    /** @see #KEY_ENABLE_NEW_OOMADJ */
+    public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ;
+
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -1308,6 +1322,7 @@
         CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
                     - rawMaxEmptyProcesses) / 3;
 
+        loadNativeBootDeviceConfigConstants();
     }
 
     public void start(ContentResolver resolver) {
@@ -1347,6 +1362,11 @@
                         DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS));
     }
 
+    private void loadNativeBootDeviceConfigConstants() {
+        ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ,
+                DEFAULT_ENABLE_NEW_OOM_ADJ);
+    }
+
     public void setOverrideMaxCachedProcesses(int value) {
         mOverrideMaxCachedProcesses = value;
         updateMaxCachedProcesses();
@@ -1997,6 +2017,13 @@
             DEFAULT_USE_MODERN_TRIM);
     }
 
+    private void updateEnableNewOomAdj() {
+        ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+            KEY_ENABLE_NEW_OOMADJ,
+            DEFAULT_ENABLE_NEW_OOM_ADJ);
+    }
+
     private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) {
         ForegroundServiceTypePolicy.getDefaultPolicy()
                 .updatePermissionEnforcementFlagIfNecessary(name);
@@ -2187,6 +2214,9 @@
         pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
         pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
 
+        pw.print("  "); pw.print(KEY_ENABLE_NEW_OOMADJ);
+        pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
+
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
             pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a0fae26..c1f2f67 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2029,6 +2029,7 @@
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
+                mOomAdjuster.onProcessBeginLocked(app);
                 updateLruProcessLocked(app, false, null);
                 updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
@@ -2422,7 +2423,9 @@
         mProcessList.init(this, activeUids, mPlatformCompat);
         mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
         mPhantomProcessList = new PhantomProcessList(this);
-        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread);
+        mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
+                : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
 
         mIntentFirewall = null;
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
@@ -2483,7 +2486,9 @@
         mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
                 new LowMemDetector(this));
         mPhantomProcessList = new PhantomProcessList(this);
-        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
+        mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
+                : new OomAdjuster(this, mProcessList, activeUids);
 
         // Broadcast policy parameters
         final BroadcastConstants foreConstants = new BroadcastConstants(
@@ -4595,6 +4600,7 @@
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
         synchronized (mProcLock) {
+            mOomAdjuster.onProcessBeginLocked(app);
             mOomAdjuster.setAttachingProcessStatesLSP(app);
             clearProcessForegroundLocked(app);
             app.setDebugging(false);
@@ -6980,6 +6986,7 @@
                     sdkSandboxClientAppPackage,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
+            mOomAdjuster.onProcessBeginLocked(app);
             updateLruProcessLocked(app, false, null);
             updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 8c1fd51..2fff79b 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -373,7 +373,7 @@
      * Return the {@link SystemProperty} name for the given key in our
      * {@link DeviceConfig} namespace.
      */
-    private @NonNull String propertyFor(@NonNull String key) {
+    private static @NonNull String propertyFor(@NonNull String key) {
         return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
     }
 
@@ -382,11 +382,11 @@
      * {@link DeviceConfig} namespace, but with a different prefix that can be
      * used to locally override the {@link DeviceConfig} value.
      */
-    private @NonNull String propertyOverrideFor(@NonNull String key) {
+    private static @NonNull String propertyOverrideFor(@NonNull String key) {
         return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
     }
 
-    private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
+    static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
         return SystemProperties.getBoolean(propertyOverrideFor(key),
                 SystemProperties.getBoolean(propertyFor(key), def));
     }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 459c6ff..1f9e89e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
@@ -124,6 +125,7 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
@@ -369,20 +371,21 @@
      */
     private final Handler mProcessGroupHandler;
 
-    private final int[] mTmpSchedGroup = new int[1];
+    protected final int[] mTmpSchedGroup = new int[1];
 
-    private final ActivityManagerService mService;
-    private final ProcessList mProcessList;
-    private final ActivityManagerGlobalLock mProcLock;
+    final ActivityManagerService mService;
+    final ProcessList mProcessList;
+    final ActivityManagerGlobalLock mProcLock;
 
     private final int mNumSlots;
-    private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
-    private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
-    private final ActiveUids mTmpUidRecords;
-    private final ArrayDeque<ProcessRecord> mTmpQueue;
-    private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
-    private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
-    private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
+    protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
+    protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>();
+    protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
+    protected final ActiveUids mTmpUidRecords;
+    protected final ArrayDeque<ProcessRecord> mTmpQueue;
+    protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
+    protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
+    protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
 
     /**
      * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate
@@ -412,7 +415,7 @@
         this(service, processList, activeUids, createAdjusterThread());
     }
 
-    private static ServiceThread createAdjusterThread() {
+    static ServiceThread createAdjusterThread() {
         // The process group is usually critical to the response time of foreground app, so the
         // setter should apply it as soon as possible.
         final ServiceThread adjusterThread =
@@ -532,7 +535,7 @@
         mPendingProcessSet.remove(app);
 
         mProcessesInCycle.clear();
-        computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true);
+        computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true);
         if (!mProcessesInCycle.isEmpty()) {
             // We can't use the score here if there is a cycle, abort.
             for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) {
@@ -550,7 +553,7 @@
                     && (uidRec.getSetProcState() != uidRec.getCurProcState()
                     || uidRec.getSetCapability() != uidRec.getCurCapability()
                     || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
-                ActiveUids uids = mTmpUidRecords;
+                final ActiveUids uids = mTmpUidRecords;
                 uids.clear();
                 uids.put(uidRec.getUid(), uidRec);
                 updateUidsLSP(uids, SystemClock.elapsedRealtime());
@@ -633,19 +636,20 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
         mAdjSeq++;
 
-        // Firstly, try to see if the importance of itself gets changed
         final ProcessStateRecord state = app.mState;
         final boolean wasCached = state.isCached();
         final int oldAdj = state.getCurRawAdj();
         final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
                 ? oldAdj : UNKNOWN_ADJ;
+
+        // Firstly, try to see if the importance of itself gets changed
         final boolean wasBackground = ActivityManager.isProcStateBackground(
                 state.getSetProcState());
         final int oldCap = state.getSetCapability();
@@ -693,8 +697,6 @@
         mPendingProcessSet.clear();
 
         if (!containsCycle) {
-            // Reset the flag
-            state.setReachable(false);
             // Remove this app from the return list because we've done the computation on it.
             processes.remove(app);
         }
@@ -718,8 +720,13 @@
         return true;
     }
 
+    /**
+     * Collect the reachable processes from the given {@code apps}, the result will be
+     * returned in the given {@code processes}, which will include the processes from
+     * the given {@code apps}.
+     */
     @GuardedBy("mService")
-    private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+    protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
             ArrayList<ProcessRecord> processes, ActiveUids uids) {
         final ArrayDeque<ProcessRecord> queue = mTmpQueue;
         queue.clear();
@@ -824,11 +831,15 @@
         if (size > 0) {
             // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it.
             for (int l = 0, r = size - 1; l < r; l++, r--) {
-                ProcessRecord t = processes.get(l);
-                processes.set(l, processes.get(r));
+                final ProcessRecord t = processes.get(l);
+                final ProcessRecord u = processes.get(r);
+                t.mState.setReachable(false);
+                u.mState.setReachable(false);
+                processes.set(l, u);
                 processes.set(r, t);
             }
         }
+
         return containsCycle;
     }
 
@@ -928,24 +939,18 @@
      * Update OomAdj for all processes within the given list (could be partial), or the whole LRU
      * list if the given list is null; when it's partial update, each process's client proc won't
      * get evaluated recursively here.
+     *
+     * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller
+     * must have called {@link collectReachableProcessesLocked} on it.
      */
     @GuardedBy({"mService", "mProcLock"})
-    private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
             boolean startProfiling) {
-        if (startProfiling) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-            mService.mOomAdjProfiler.oomAdjStarted();
-        }
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
         final boolean fullUpdate = processes == null;
+        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+                ? mProcessList.getLruProcessesLOSP() : processes;
         ActiveUids activeUids = uids;
-        ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP()
-                : processes;
-        final int numProc = activeProcesses.size();
-
         if (activeUids == null) {
             final int numUids = mActiveUids.size();
             activeUids = mTmpUidRecords;
@@ -956,14 +961,14 @@
             }
         }
 
-        // Reset state in all uid records.
-        for (int  i = activeUids.size() - 1; i >= 0; i--) {
-            final UidRecord uidRec = activeUids.valueAt(i);
-            if (DEBUG_UID_OBSERVERS) {
-                Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-            }
-            uidRec.reset();
+        if (startProfiling) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+            mService.mOomAdjProfiler.oomAdjStarted();
         }
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+        final int numProc = activeProcesses.size();
 
         mAdjSeq++;
         if (fullUpdate) {
@@ -971,6 +976,9 @@
             mNewNumAServiceProcs = 0;
         }
 
+        // Reset state in all uid records.
+        resetUidRecordsLsp(activeUids);
+
         boolean retryCycles = false;
         boolean computeClients = fullUpdate || potentialCycles;
 
@@ -996,8 +1004,9 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 state.setProcStateChanged(false);
                 app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
+                // It won't enter cycle if not computing clients.
                 computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false,
-                        computeClients); // It won't enter cycle if not computing clients.
+                        computeClients, oomAdjReason, true);
                 // if any app encountered a cycle, we need to perform an additional loop later
                 retryCycles |= state.containsCycle();
                 // Keep the completedAdjSeq to up to date.
@@ -1034,7 +1043,7 @@
                     final ProcessStateRecord state = app.mState;
                     if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
                         if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now,
-                                true, true)) {
+                                true, true, oomAdjReason, true)) {
                             retryCycles = true;
                         }
                     }
@@ -1045,10 +1054,33 @@
 
         assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
 
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+
+        if (startProfiling) {
+            mService.mOomAdjProfiler.oomAdjEnded();
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) {
+        // Reset state in all uid records.
+        for (int  i = activeUids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = activeUids.valueAt(i);
+            if (DEBUG_UID_OBSERVERS) {
+                Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+            }
+            uidRec.reset();
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
+            long now, long nowElapsed, long oldTime) {
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
-        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+        final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
                 oomAdjReason);
         mNumServiceProcs = mNewNumServiceProcs;
 
@@ -1085,14 +1117,10 @@
                 Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
             }
         }
-        if (startProfiling) {
-            mService.mOomAdjProfiler.oomAdjEnded();
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+    protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = SystemClock.uptimeMillis();
@@ -1413,7 +1441,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
+    protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
         if (!app.isKilledByAm() && app.getThread() != null) {
             if (app.isolated && app.mServices.numberOfRunningServices() <= 0
                     && app.getIsolatedEntryPoint() == null) {
@@ -1442,7 +1470,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
+    protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
         // This compares previously set procstate to the current procstate in regards to whether
         // or not the app's network access will be blocked. So, this needs to be called before
         // we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}.
@@ -1580,7 +1608,7 @@
         return true;
     }
 
-    private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
+    protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
             new ComputeOomAdjWindowCallback();
 
     /** These methods are called inline during computeOomAdjLSP(), on the same thread */
@@ -1719,24 +1747,30 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
+    protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
-            boolean computeClients) {
+            boolean computeClients, int oomAdjReason, boolean couldRecurse) {
         final ProcessStateRecord state = app.mState;
-        if (mAdjSeq == state.getAdjSeq()) {
-            if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
-                // This adjustment has already been computed successfully.
-                return false;
-            } else {
-                // The process is being computed, so there is a cycle. We cannot
-                // rely on this process's state.
-                state.setContainsCycle(true);
-                mProcessesInCycle.add(app);
+        if (couldRecurse) {
+            if (mAdjSeq == state.getAdjSeq()) {
+                if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
+                    // This adjustment has already been computed successfully.
+                    return false;
+                } else {
+                    // The process is being computed, so there is a cycle. We cannot
+                    // rely on this process's state.
+                    state.setContainsCycle(true);
+                    mProcessesInCycle.add(app);
 
-                return false;
+                    return false;
+                }
             }
         }
 
+        int prevAppAdj = getInitialAdj(app);
+        int prevProcState = getInitialProcState(app);
+        int prevCapability = getInitialCapability(app);
+
         if (app.getThread() == null) {
             state.setAdjSeq(mAdjSeq);
             state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
@@ -1745,6 +1779,8 @@
             state.setCurRawAdj(CACHED_APP_MAX_ADJ);
             state.setCompletedAdjSeq(state.getAdjSeq());
             state.setCurCapability(PROCESS_CAPABILITY_NONE);
+            onProcessStateChanged(app, prevProcState);
+            onProcessOomAdjChanged(app, prevAppAdj);
             return false;
         }
 
@@ -1753,7 +1789,7 @@
         state.setAdjTarget(null);
         state.setEmpty(false);
         state.setCached(false);
-        if (!cycleReEval) {
+        if (!couldRecurse || !cycleReEval) {
             // Don't reset this flag when doing cycles re-evaluation.
             state.setNoKillOnBgRestrictedAndIdle(false);
             // If this UID is currently allowlisted, it should not be frozen.
@@ -1764,9 +1800,6 @@
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
 
-        int prevAppAdj = state.getCurAdj();
-        int prevProcState = state.getCurProcState();
-        int prevCapability = state.getCurCapability();
         final ProcessServiceRecord psr = app.mServices;
 
         if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) {
@@ -1812,6 +1845,8 @@
             state.setCurRawProcState(state.getCurProcState());
             state.setCurAdj(state.getMaxAdj());
             state.setCompletedAdjSeq(state.getAdjSeq());
+            onProcessStateChanged(app, prevProcState);
+            onProcessOomAdjChanged(app, prevAppAdj);
             // if curAdj is less than prevAppAdj, then this process was promoted
             return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
         }
@@ -1825,7 +1860,7 @@
         int adj;
         int schedGroup;
         int procState;
-        int capability = cycleReEval ? app.mState.getCurCapability() : 0;
+        int capability = cycleReEval ? getInitialCapability(app) : 0;
 
         boolean foregroundActivities = false;
         boolean hasVisibleActivities = false;
@@ -1904,7 +1939,7 @@
             // value that the caller wants us to.
             adj = cachedAdj;
             procState = PROCESS_STATE_CACHED_EMPTY;
-            if (!state.containsCycle()) {
+            if (!couldRecurse || !state.containsCycle()) {
                 state.setCached(true);
                 state.setEmpty(true);
                 state.setAdjType("cch-empty");
@@ -2169,8 +2204,10 @@
             }
         }
 
-        boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp();
-        boolean scheduleLikeTopApp = false;
+        state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app));
+
+        state.setScheduleLikeTopApp(false);
+
         for (int is = psr.numberOfRunningServices() - 1;
                 is >= 0 && (adj > FOREGROUND_APP_ADJ
                         || schedGroup == SCHED_GROUP_BACKGROUND
@@ -2243,6 +2280,18 @@
                 }
             }
 
+            if (!couldRecurse) {
+                // We're entering recursive functions below, if we're told it's not a recursive
+                // loop, abort here.
+                continue;
+            }
+
+
+            state.setCurRawAdj(adj);
+            state.setCurRawProcState(procState);
+            state.setCurrentSchedulingGroup(schedGroup);
+            state.setCurCapability(capability);
+
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
             for (int conni = serviceConnections.size() - 1;
                     conni >= 0 && (adj > FOREGROUND_APP_ADJ
@@ -2263,335 +2312,13 @@
                         continue;
                     }
 
-                    boolean trackedProcState = false;
+                    computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
 
-                    ProcessRecord client = cr.binding.client;
-                    if (app.isSdkSandbox && cr.binding.attributedClient != null) {
-                        // For SDK sandboxes, use the attributed client (eg the app that
-                        // requested the sandbox)
-                        client = cr.binding.attributedClient;
-                    }
-                    final ProcessStateRecord cstate = client.mState;
-                    if (computeClients) {
-                        computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
-                                cycleReEval, true);
-                    } else {
-                        cstate.setCurRawAdj(cstate.getCurAdj());
-                        cstate.setCurRawProcState(cstate.getCurProcState());
-                    }
-
-                    int clientAdj = cstate.getCurRawAdj();
-                    int clientProcState = cstate.getCurRawProcState();
-
-                    final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
-
-                    boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
-                            || clientProcState <= PROCESS_STATE_BOUND_TOP
-                            || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                                    && !cstate.isBackgroundRestricted());
-
-                    if (client.mOptRecord.shouldNotFreeze()) {
-                        // Propagate the shouldNotFreeze flag down the bindings.
-                        app.mOptRecord.setShouldNotFreeze(true);
-                    }
-
-                    // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
-                    // but, right before actually setting it to the process,
-                    // we check the final procstate, and remove it if the procsate is below BFGS.
-                    capability |= getBfslCapabilityFromClient(client);
-
-                    if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
-                        if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
-                            capability |= cstate.getCurCapability();
-                        }
-
-                        // If an app has network capability by default
-                        // (by having procstate <= BFGS), then the apps it binds to will get
-                        // elevated to a high enough procstate anyway to get network unless they
-                        // request otherwise, so don't propagate the network capability by default
-                        // in this case unless they explicitly request it.
-                        if ((cstate.getCurCapability()
-                                & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
-                            if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
-                                // This is used to grant network access to Expedited Jobs.
-                                if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
-                                    capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
-                                }
-                            } else {
-                                capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
-                            }
-                        }
-                        if ((cstate.getCurCapability()
-                                & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
-                            if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                                // This is used to grant network access to User Initiated Jobs.
-                                if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
-                                    capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
-                                }
-                            }
-                        }
-
-                        if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                            continue;
-                        }
-
-                        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                            // If the other app is cached for any reason, for purposes here
-                            // we are going to consider it empty.  The specific cached state
-                            // doesn't propagate except under certain conditions.
-                            clientProcState = PROCESS_STATE_CACHED_EMPTY;
-                        }
-                        String adjType = null;
-                        if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
-                            // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
-                            if (clientAdj < CACHED_APP_MIN_ADJ) {
-                                app.mOptRecord.setShouldNotFreeze(true);
-                            }
-                            // Not doing bind OOM management, so treat
-                            // this guy more like a started service.
-                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
-                                // If this process has shown some UI, let it immediately
-                                // go to the LRU list because it may be pretty heavy with
-                                // UI stuff.  We'll tag it with a label just to help
-                                // debug and understand what is going on.
-                                if (adj > clientAdj) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                                state.setCached(false);
-                                clientAdj = adj;
-                                clientProcState = procState;
-                            } else {
-                                if (now >= (s.lastActivity
-                                        + mConstants.MAX_SERVICE_INACTIVITY)) {
-                                    // This service has not seen activity within
-                                    // recent memory, so allow it to drop to the
-                                    // LRU list if there is no other reason to keep
-                                    // it around.  We'll also tag it with a label just
-                                    // to help debug and undertand what is going on.
-                                    if (adj > clientAdj) {
-                                        adjType = "cch-bound-services";
-                                    }
-                                    clientAdj = adj;
-                                }
-                            }
-                        }
-                        if (adj > clientAdj) {
-                            // If this process has recently shown UI, and
-                            // the process that is binding to it is less
-                            // important than being visible, then we don't
-                            // care about the binding as much as we care
-                            // about letting this process get into the LRU
-                            // list to be killed and restarted if needed for
-                            // memory.
-                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                                    && clientAdj > PERCEPTIBLE_APP_ADJ) {
-                                if (adj >= CACHED_APP_MIN_ADJ) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                            } else {
-                                int newAdj;
-                                int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
-                                if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
-                                        | Context.BIND_IMPORTANT)) {
-                                    if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
-                                        newAdj = clientAdj;
-                                    } else {
-                                        // make this service persistent
-                                        newAdj = PERSISTENT_SERVICE_ADJ;
-                                        schedGroup = SCHED_GROUP_DEFAULT;
-                                        procState = ActivityManager.PROCESS_STATE_PERSISTENT;
-                                        cr.trackProcState(procState, mAdjSeq);
-                                        trackedProcState = true;
-                                    }
-                                } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
-                                        && clientAdj <= PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
-                                    newAdj = PERCEPTIBLE_LOW_APP_ADJ;
-                                } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
-                                        && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
-                                    // This is for user-initiated jobs.
-                                    // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
-                                    newAdj = PERCEPTIBLE_APP_ADJ + 1;
-                                } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
-                                        && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
-                                    // This is for expedited jobs.
-                                    // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
-                                    // EJ and short-FGS.
-                                    newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
-                                } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
-                                    newAdj = PERCEPTIBLE_APP_ADJ;
-                                } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
-                                    newAdj = clientAdj;
-                                } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
-                                        && clientAdj <= VISIBLE_APP_ADJ
-                                        && adj > VISIBLE_APP_ADJ) {
-                                    newAdj = VISIBLE_APP_ADJ;
-                                } else {
-                                    if (adj > VISIBLE_APP_ADJ) {
-                                        // TODO: Is this too limiting for apps bound from TOP?
-                                        newAdj = Math.max(clientAdj, lbAdj);
-                                    } else {
-                                        newAdj = adj;
-                                    }
-                                }
-                                if (!cstate.isCached()) {
-                                    state.setCached(false);
-                                }
-                                if (adj >  newAdj) {
-                                    adj = newAdj;
-                                    state.setCurRawAdj(adj);
-                                    adjType = "service";
-                                }
-                            }
-                        }
-                        if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
-                                | Context.BIND_IMPORTANT_BACKGROUND)) {
-                            // This will treat important bound services identically to
-                            // the top app, which may behave differently than generic
-                            // foreground work.
-                            final int curSchedGroup = cstate.getCurrentSchedulingGroup();
-                            if (curSchedGroup > schedGroup) {
-                                if (cr.hasFlag(Context.BIND_IMPORTANT)) {
-                                    schedGroup = curSchedGroup;
-                                } else {
-                                    schedGroup = SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            if (clientProcState < PROCESS_STATE_TOP) {
-                                // Special handling for above-top states (persistent
-                                // processes).  These should not bring the current process
-                                // into the top state, since they are not on top.  Instead
-                                // give them the best bound state after that.
-                                if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
-                                    clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
-                                } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
-                                    clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                } else if (mService.mWakefulness.get()
-                                        == PowerManagerInternal.WAKEFULNESS_AWAKE
-                                        && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE))
-                                {
-                                    clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                } else {
-                                    clientProcState =
-                                            PROCESS_STATE_IMPORTANT_FOREGROUND;
-                                }
-                            } else if (clientProcState == PROCESS_STATE_TOP) {
-                                // Go at most to BOUND_TOP, unless requested to elevate
-                                // to client's state.
-                                clientProcState = PROCESS_STATE_BOUND_TOP;
-                                final boolean enabled = cstate.getCachedCompatChange(
-                                        CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
-                                if (enabled) {
-                                    if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
-                                        // TOP process passes all capabilities to the service.
-                                        capability |= cstate.getCurCapability();
-                                    } else {
-                                        // TOP process passes no capability to the service.
-                                    }
-                                } else {
-                                    // TOP process passes all capabilities to the service.
-                                    capability |= cstate.getCurCapability();
-                                }
-                            }
-                        } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
-                            if (clientProcState <
-                                    PROCESS_STATE_TRANSIENT_BACKGROUND) {
-                                clientProcState =
-                                        PROCESS_STATE_TRANSIENT_BACKGROUND;
-                            }
-                        } else {
-                            if (clientProcState <
-                                    PROCESS_STATE_IMPORTANT_BACKGROUND) {
-                                clientProcState =
-                                        PROCESS_STATE_IMPORTANT_BACKGROUND;
-                            }
-                        }
-
-                        if (schedGroup < SCHED_GROUP_TOP_APP
-                                && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
-                                && clientIsSystem) {
-                            schedGroup = SCHED_GROUP_TOP_APP;
-                            scheduleLikeTopApp = true;
-                        }
-
-                        if (!trackedProcState) {
-                            cr.trackProcState(clientProcState, mAdjSeq);
-                        }
-
-                        if (procState > clientProcState) {
-                            procState = clientProcState;
-                            state.setCurRawProcState(procState);
-                            if (adjType == null) {
-                                adjType = "service";
-                            }
-                        }
-                        if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
-                                && cr.hasFlag(Context.BIND_SHOWING_UI)) {
-                            app.setPendingUiClean(true);
-                        }
-                        if (adjType != null) {
-                            state.setAdjType(adjType);
-                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE);
-                            state.setAdjSource(client);
-                            state.setAdjSourceProcState(clientProcState);
-                            state.setAdjTarget(s.instanceName);
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                        + ": " + app + ", due to " + client
-                                        + " adj=" + adj + " procState="
-                                        + ProcessList.makeProcStateString(procState));
-                            }
-                        }
-                    } else { // BIND_WAIVE_PRIORITY == true
-                        // BIND_WAIVE_PRIORITY bindings are special when it comes to the
-                        // freezer. Processes bound via WPRI are expected to be running,
-                        // but they are not promoted in the LRU list to keep them out of
-                        // cached. As a result, they can freeze based on oom_adj alone.
-                        // Normally, bindToDeath would fire when a cached app would die
-                        // in the background, but nothing will fire when a running process
-                        // pings a frozen process. Accordingly, any cached app that is
-                        // bound by an unfrozen app via a WPRI binding has to remain
-                        // unfrozen.
-                        if (clientAdj < CACHED_APP_MIN_ADJ) {
-                            app.mOptRecord.setShouldNotFreeze(true);
-                        }
-                    }
-                    if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
-                        psr.setTreatLikeActivity(true);
-                    }
-                    final ActivityServiceConnectionsHolder a = cr.activity;
-                    if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
-                        if (a != null && adj > FOREGROUND_APP_ADJ
-                                && a.isActivityVisible()) {
-                            adj = FOREGROUND_APP_ADJ;
-                            state.setCurRawAdj(adj);
-                            if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
-                                if (cr.hasFlag(Context.BIND_IMPORTANT)) {
-                                    schedGroup = SCHED_GROUP_TOP_APP_BOUND;
-                                } else {
-                                    schedGroup = SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            state.setCached(false);
-                            state.setAdjType("service");
-                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE);
-                            state.setAdjSource(a);
-                            state.setAdjSourceProcState(procState);
-                            state.setAdjTarget(s.instanceName);
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                        "Raise to service w/activity: " + app);
-                            }
-                        }
-                    }
+                    adj = state.getCurRawAdj();
+                    procState = state.getCurRawProcState();
+                    schedGroup = state.getCurrentSchedulingGroup();
+                    capability = state.getCurCapability();
                 }
             }
         }
@@ -2603,97 +2330,27 @@
                         || procState > PROCESS_STATE_TOP);
                 provi--) {
             ContentProviderRecord cpr = ppr.getProviderAt(provi);
-            for (int i = cpr.connections.size() - 1;
-                    i >= 0 && (adj > FOREGROUND_APP_ADJ
-                            || schedGroup == SCHED_GROUP_BACKGROUND
-                            || procState > PROCESS_STATE_TOP);
-                    i--) {
-                ContentProviderConnection conn = cpr.connections.get(i);
-                ProcessRecord client = conn.client;
-                final ProcessStateRecord cstate = client.mState;
-                if (client == app) {
-                    // Being our own client is not interesting.
-                    continue;
-                }
-                if (computeClients) {
-                    computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
-                } else {
-                    cstate.setCurRawAdj(cstate.getCurAdj());
-                    cstate.setCurRawProcState(cstate.getCurProcState());
-                }
+            if (couldRecurse) {
+                // We're entering recursive functions below.
+                state.setCurRawAdj(adj);
+                state.setCurRawProcState(procState);
+                state.setCurrentSchedulingGroup(schedGroup);
+                state.setCurCapability(capability);
 
-                if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                    continue;
-                }
+                for (int i = cpr.connections.size() - 1;
+                        i >= 0 && (adj > FOREGROUND_APP_ADJ
+                                || schedGroup == SCHED_GROUP_BACKGROUND
+                                || procState > PROCESS_STATE_TOP);
+                        i--) {
+                    ContentProviderConnection conn = cpr.connections.get(i);
+                    ProcessRecord client = conn.client;
+                    computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll,
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
 
-                int clientAdj = cstate.getCurRawAdj();
-                int clientProcState = cstate.getCurRawProcState();
-
-                // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
-                // but, right before actually setting it to the process,
-                // we check the final procstate, and remove it if the procsate is below BFGS.
-                capability |= getBfslCapabilityFromClient(client);
-
-                if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                    // If the other app is cached for any reason, for purposes here
-                    // we are going to consider it empty.
-                    clientProcState = PROCESS_STATE_CACHED_EMPTY;
-                }
-                if (client.mOptRecord.shouldNotFreeze()) {
-                    // Propagate the shouldNotFreeze flag down the bindings.
-                    app.mOptRecord.setShouldNotFreeze(true);
-                }
-
-                boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
-                        || clientProcState <= PROCESS_STATE_BOUND_TOP
-                        || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                                && !cstate.isBackgroundRestricted());
-
-                String adjType = null;
-                if (adj > clientAdj) {
-                    if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                            && clientAdj > PERCEPTIBLE_APP_ADJ) {
-                        adjType = "cch-ui-provider";
-                    } else {
-                        adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
-                        state.setCurRawAdj(adj);
-                        adjType = "provider";
-                    }
-                    state.setCached(state.isCached() & cstate.isCached());
-                }
-
-                if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
-                    if (adjType == null) {
-                        adjType = "provider";
-                    }
-                    if (clientProcState == PROCESS_STATE_TOP) {
-                        clientProcState = PROCESS_STATE_BOUND_TOP;
-                    } else {
-                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                    }
-                }
-
-                conn.trackProcState(clientProcState, mAdjSeq);
-                if (procState > clientProcState) {
-                    procState = clientProcState;
-                    state.setCurRawProcState(procState);
-                }
-                if (cstate.getCurrentSchedulingGroup() > schedGroup) {
-                    schedGroup = SCHED_GROUP_DEFAULT;
-                }
-                if (adjType != null) {
-                    state.setAdjType(adjType);
-                    state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                            .REASON_PROVIDER_IN_USE);
-                    state.setAdjSource(client);
-                    state.setAdjSourceProcState(clientProcState);
-                    state.setAdjTarget(cpr.name);
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                + ": " + app + ", due to " + client
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
+                    adj = state.getCurRawAdj();
+                    procState = state.getCurRawProcState();
+                    schedGroup = state.getCurrentSchedulingGroup();
+                    capability = state.getCurCapability();
                 }
             }
             // If the provider has external (non-framework) process
@@ -2799,7 +2456,7 @@
         // restrictions on screen off
         if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                 && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
-                && !scheduleLikeTopApp) {
+                && !state.shouldScheduleLikeTopApp()) {
             if (schedGroup > SCHED_GROUP_RESTRICTED) {
                 schedGroup = SCHED_GROUP_RESTRICTED;
             }
@@ -2817,6 +2474,7 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
+        state.setHasForegroundActivities(foregroundActivities);
 
         if (app.isPendingFinishAttach()) {
             // If the app is still starting up. We reset the computations to the
@@ -2834,22 +2492,580 @@
         // it when computing the final cached adj later.  Note that we don't need to
         // worry about this for max adj above, since max adj will always be used to
         // keep it out of the cached vaues.
-        state.setCurAdj(adj);
         state.setCurCapability(capability);
-        state.setCurrentSchedulingGroup(schedGroup);
-        state.setCurProcState(procState);
-        state.setCurRawProcState(procState);
         state.updateLastInvisibleTime(hasVisibleActivities);
-        state.setHasForegroundActivities(foregroundActivities);
         state.setCompletedAdjSeq(mAdjSeq);
-        state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted);
+
+        schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup);
+        setIntermediateProcStateLSP(app, procState, prevProcState);
+        setIntermediateSchedGroupLSP(state, schedGroup);
 
         // if curAdj or curProcState improved, then this process was promoted
         return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState
                 || state.getCurCapability() != prevCapability;
     }
 
-    private int getDefaultCapability(ProcessRecord app, int procState) {
+    /**
+     * @return The proposed change to the schedGroup.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+            int schedGroup) {
+        final ProcessStateRecord state = app.mState;
+        state.setCurRawAdj(adj);
+
+        adj = app.mServices.modifyRawOomAdj(adj);
+        if (adj > state.getMaxAdj()) {
+            adj = state.getMaxAdj();
+            if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
+                schedGroup = SCHED_GROUP_DEFAULT;
+            }
+        }
+
+        state.setCurAdj(adj);
+
+        return schedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+            int prevProcState) {
+        final ProcessStateRecord state = app.mState;
+        state.setCurProcState(procState);
+        state.setCurRawProcState(procState);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) {
+        // Put bound foreground services in a special sched group for additional
+        // restrictions on screen off
+        if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+                && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+                && !state.shouldScheduleLikeTopApp()) {
+            if (schedGroup > SCHED_GROUP_RESTRICTED) {
+                schedGroup = SCHED_GROUP_RESTRICTED;
+            }
+        }
+
+        state.setCurrentSchedulingGroup(schedGroup);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+            boolean couldRecurse) {
+        if (app.isPendingFinishAttach()) {
+            // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+            return;
+        }
+
+        final ProcessStateRecord state = app.mState;
+        ProcessStateRecord cstate = client.mState;
+
+        if (couldRecurse) {
+            if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+                // For SDK sandboxes, use the attributed client (eg the app that
+                // requested the sandbox)
+                client = cr.binding.attributedClient;
+                cstate = client.mState;
+            }
+            if (computeClients) {
+                computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+                        oomAdjReason, true);
+            } else {
+                cstate.setCurRawAdj(cstate.getCurAdj());
+                cstate.setCurRawProcState(cstate.getCurProcState());
+            }
+        }
+
+        int clientAdj = cstate.getCurRawAdj();
+        int clientProcState = cstate.getCurRawProcState();
+
+        final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
+
+        int adj = state.getCurRawAdj();
+        int procState = state.getCurRawProcState();
+        int schedGroup = state.getCurrentSchedulingGroup();
+        int capability = state.getCurCapability();
+
+        final int prevRawAdj = adj;
+        final int prevProcState = procState;
+        final int prevSchedGroup = schedGroup;
+
+        final int appUid = app.info.uid;
+        final int logUid = mService.mCurOomAdjUid;
+
+        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                || cstate.isCurBoundByNonBgRestrictedApp()
+                || clientProcState <= PROCESS_STATE_BOUND_TOP
+                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                        && !cstate.isBackgroundRestricted()));
+
+        if (client.mOptRecord.shouldNotFreeze()) {
+            // Propagate the shouldNotFreeze flag down the bindings.
+            app.mOptRecord.setShouldNotFreeze(true);
+        }
+
+        boolean trackedProcState = false;
+
+        // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
+        // but, right before actually setting it to the process,
+        // we check the final procstate, and remove it if the procsate is below BFGS.
+        capability |= getBfslCapabilityFromClient(client);
+
+        if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
+            if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                capability |= cstate.getCurCapability();
+            }
+
+            // If an app has network capability by default
+            // (by having procstate <= BFGS), then the apps it binds to will get
+            // elevated to a high enough procstate anyway to get network unless they
+            // request otherwise, so don't propagate the network capability by default
+            // in this case unless they explicitly request it.
+            if ((cstate.getCurCapability()
+                    & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
+                if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+                    // This is used to grant network access to Expedited Jobs.
+                    if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
+                        capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+                    }
+                } else {
+                    capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+                }
+            }
+            if ((cstate.getCurCapability()
+                    & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
+                if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    // This is used to grant network access to User Initiated Jobs.
+                    if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+                        capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+                    }
+                }
+            }
+
+            if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+                return;
+            }
+
+            if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+                // If the other app is cached for any reason, for purposes here
+                // we are going to consider it empty.  The specific cached state
+                // doesn't propagate except under certain conditions.
+                clientProcState = PROCESS_STATE_CACHED_EMPTY;
+            }
+            String adjType = null;
+            if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
+                // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
+                if (clientAdj < CACHED_APP_MIN_ADJ) {
+                    app.mOptRecord.setShouldNotFreeze(true);
+                }
+                // Not doing bind OOM management, so treat
+                // this guy more like a started service.
+                if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
+                    // If this process has shown some UI, let it immediately
+                    // go to the LRU list because it may be pretty heavy with
+                    // UI stuff.  We'll tag it with a label just to help
+                    // debug and understand what is going on.
+                    if (adj > clientAdj) {
+                        adjType = "cch-bound-ui-services";
+                    }
+                    state.setCached(false);
+                    clientAdj = adj;
+                    clientProcState = procState;
+                } else {
+                    if (now >= (cr.binding.service.lastActivity
+                            + mConstants.MAX_SERVICE_INACTIVITY)) {
+                        // This service has not seen activity within
+                        // recent memory, so allow it to drop to the
+                        // LRU list if there is no other reason to keep
+                        // it around.  We'll also tag it with a label just
+                        // to help debug and undertand what is going on.
+                        if (adj > clientAdj) {
+                            adjType = "cch-bound-services";
+                        }
+                        clientAdj = adj;
+                    }
+                }
+            }
+            if (adj > clientAdj) {
+                // If this process has recently shown UI, and
+                // the process that is binding to it is less
+                // important than being visible, then we don't
+                // care about the binding as much as we care
+                // about letting this process get into the LRU
+                // list to be killed and restarted if needed for
+                // memory.
+                if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+                        && clientAdj > PERCEPTIBLE_APP_ADJ) {
+                    if (adj >= CACHED_APP_MIN_ADJ) {
+                        adjType = "cch-bound-ui-services";
+                    }
+                } else {
+                    int newAdj;
+                    int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
+                    if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
+                            | Context.BIND_IMPORTANT)) {
+                        if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
+                            newAdj = clientAdj;
+                        } else {
+                            // make this service persistent
+                            newAdj = PERSISTENT_SERVICE_ADJ;
+                            schedGroup = SCHED_GROUP_DEFAULT;
+                            procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+                            cr.trackProcState(procState, mAdjSeq);
+                            trackedProcState = true;
+                        }
+                    } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
+                            && clientAdj <= PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
+                        newAdj = PERCEPTIBLE_LOW_APP_ADJ;
+                    } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+                            && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+                        // This is for user-initiated jobs.
+                        // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
+                        newAdj = PERCEPTIBLE_APP_ADJ + 1;
+                    } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+                            && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
+                        // This is for expedited jobs.
+                        // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
+                        // EJ and short-FGS.
+                        newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
+                    } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+                        newAdj = PERCEPTIBLE_APP_ADJ;
+                    } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
+                        newAdj = clientAdj;
+                    } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+                            && clientAdj <= VISIBLE_APP_ADJ
+                            && adj > VISIBLE_APP_ADJ) {
+                        newAdj = VISIBLE_APP_ADJ;
+                    } else {
+                        if (adj > VISIBLE_APP_ADJ) {
+                            // TODO: Is this too limiting for apps bound from TOP?
+                            newAdj = Math.max(clientAdj, lbAdj);
+                        } else {
+                            newAdj = adj;
+                        }
+                    }
+                    if (!cstate.isCached()) {
+                        state.setCached(false);
+                    }
+                    if (adj >  newAdj) {
+                        adj = newAdj;
+                        state.setCurRawAdj(adj);
+                        adjType = "service";
+                    }
+                }
+            }
+            if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_IMPORTANT_BACKGROUND)) {
+                // This will treat important bound services identically to
+                // the top app, which may behave differently than generic
+                // foreground work.
+                final int curSchedGroup = cstate.getCurrentSchedulingGroup();
+                if (curSchedGroup > schedGroup) {
+                    if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+                        schedGroup = curSchedGroup;
+                    } else {
+                        schedGroup = SCHED_GROUP_DEFAULT;
+                    }
+                }
+                if (clientProcState < PROCESS_STATE_TOP) {
+                    // Special handling for above-top states (persistent
+                    // processes).  These should not bring the current process
+                    // into the top state, since they are not on top.  Instead
+                    // give them the best bound state after that.
+                    if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+                        clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+                    } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                    } else if (mService.mWakefulness.get()
+                            == PowerManagerInternal.WAKEFULNESS_AWAKE
+                            && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
+                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                    } else {
+                        clientProcState =
+                                PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    }
+                } else if (clientProcState == PROCESS_STATE_TOP) {
+                    // Go at most to BOUND_TOP, unless requested to elevate
+                    // to client's state.
+                    clientProcState = PROCESS_STATE_BOUND_TOP;
+                    final boolean enabled = cstate.getCachedCompatChange(
+                            CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
+                    if (enabled) {
+                        if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                            // TOP process passes all capabilities to the service.
+                            capability |= cstate.getCurCapability();
+                        } else {
+                            // TOP process passes no capability to the service.
+                        }
+                    } else {
+                        // TOP process passes all capabilities to the service.
+                        capability |= cstate.getCurCapability();
+                    }
+                }
+            } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
+                if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                    clientProcState =
+                            PROCESS_STATE_TRANSIENT_BACKGROUND;
+                }
+            } else {
+                if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                    clientProcState =
+                            PROCESS_STATE_IMPORTANT_BACKGROUND;
+                }
+            }
+
+            if (schedGroup < SCHED_GROUP_TOP_APP
+                    && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
+                    && clientIsSystem) {
+                schedGroup = SCHED_GROUP_TOP_APP;
+                state.setScheduleLikeTopApp(true);
+            }
+
+            if (!trackedProcState) {
+                cr.trackProcState(clientProcState, mAdjSeq);
+            }
+
+            if (procState > clientProcState) {
+                procState = clientProcState;
+                state.setCurRawProcState(procState);
+                if (adjType == null) {
+                    adjType = "service";
+                }
+            }
+            if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
+                    && cr.hasFlag(Context.BIND_SHOWING_UI)) {
+                app.setPendingUiClean(true);
+            }
+            if (adjType != null) {
+                state.setAdjType(adjType);
+                state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                        .REASON_SERVICE_IN_USE);
+                state.setAdjSource(client);
+                state.setAdjSourceProcState(clientProcState);
+                state.setAdjTarget(cr.binding.service.instanceName);
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                            + ": " + app + ", due to " + client
+                            + " adj=" + adj + " procState="
+                            + ProcessList.makeProcStateString(procState));
+                }
+            }
+        } else { // BIND_WAIVE_PRIORITY == true
+            // BIND_WAIVE_PRIORITY bindings are special when it comes to the
+            // freezer. Processes bound via WPRI are expected to be running,
+            // but they are not promoted in the LRU list to keep them out of
+            // cached. As a result, they can freeze based on oom_adj alone.
+            // Normally, bindToDeath would fire when a cached app would die
+            // in the background, but nothing will fire when a running process
+            // pings a frozen process. Accordingly, any cached app that is
+            // bound by an unfrozen app via a WPRI binding has to remain
+            // unfrozen.
+            if (clientAdj < CACHED_APP_MIN_ADJ) {
+                app.mOptRecord.setShouldNotFreeze(true);
+            }
+        }
+        if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
+            app.mServices.setTreatLikeActivity(true);
+            if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY
+                    && procState > PROCESS_STATE_CACHED_ACTIVITY) {
+                // This is a cached process, but somebody wants us to treat it like it has
+                // an activity, okay!
+                procState = PROCESS_STATE_CACHED_ACTIVITY;
+                state.setAdjType("cch-as-act");
+            }
+        }
+        final ActivityServiceConnectionsHolder a = cr.activity;
+        if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
+            if (a != null && adj > FOREGROUND_APP_ADJ
+                    && a.isActivityVisible()) {
+                adj = FOREGROUND_APP_ADJ;
+                state.setCurRawAdj(adj);
+                if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
+                    if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+                        schedGroup = SCHED_GROUP_TOP_APP_BOUND;
+                    } else {
+                        schedGroup = SCHED_GROUP_DEFAULT;
+                    }
+                }
+                state.setCached(false);
+                state.setAdjType("service");
+                state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                        .REASON_SERVICE_IN_USE);
+                state.setAdjSource(a);
+                state.setAdjSourceProcState(procState);
+                state.setAdjTarget(cr.binding.service.instanceName);
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise to service w/activity: " + app);
+                }
+            }
+        }
+
+        capability |= getDefaultCapability(app, procState);
+
+        // Procstates below BFGS should never have this capability.
+        if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+            capability &= ~PROCESS_CAPABILITY_BFSL;
+        }
+
+        if (adj < prevRawAdj) {
+            schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+        }
+        if (procState < prevProcState) {
+            setIntermediateProcStateLSP(app, procState, prevProcState);
+        }
+        if (schedGroup > prevSchedGroup) {
+            setIntermediateSchedGroupLSP(state, schedGroup);
+        }
+        state.setCurCapability(capability);
+
+        state.setEmpty(false);
+    }
+
+    protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+            boolean couldRecurse) {
+        if (app.isPendingFinishAttach()) {
+            // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+            return;
+        }
+
+        final ProcessStateRecord state = app.mState;
+        final ProcessStateRecord cstate = client.mState;
+
+        if (client == app) {
+            // Being our own client is not interesting.
+            return;
+        }
+        if (couldRecurse) {
+            if (computeClients) {
+                computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+                        oomAdjReason, true);
+            } else if (couldRecurse) {
+                cstate.setCurRawAdj(cstate.getCurAdj());
+                cstate.setCurRawProcState(cstate.getCurProcState());
+            }
+
+            if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(),
+                    cycleReEval)) {
+                return;
+            }
+        }
+
+        int clientAdj = cstate.getCurRawAdj();
+        int clientProcState = cstate.getCurRawProcState();
+
+        int adj = state.getCurRawAdj();
+        int procState = state.getCurRawProcState();
+        int schedGroup = state.getCurrentSchedulingGroup();
+        int capability = state.getCurCapability();
+
+        final int prevRawAdj = adj;
+        final int prevProcState = procState;
+        final int prevSchedGroup = schedGroup;
+
+        final int appUid = app.info.uid;
+        final int logUid = mService.mCurOomAdjUid;
+
+        // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
+        // but, right before actually setting it to the process,
+        // we check the final procstate, and remove it if the procsate is below BFGS.
+        capability |= getBfslCapabilityFromClient(client);
+
+        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+            // If the other app is cached for any reason, for purposes here
+            // we are going to consider it empty.
+            clientProcState = PROCESS_STATE_CACHED_EMPTY;
+        }
+        if (client.mOptRecord.shouldNotFreeze()) {
+            // Propagate the shouldNotFreeze flag down the bindings.
+            app.mOptRecord.setShouldNotFreeze(true);
+        }
+
+        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                || cstate.isCurBoundByNonBgRestrictedApp()
+                || clientProcState <= PROCESS_STATE_BOUND_TOP
+                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                        && !cstate.isBackgroundRestricted()));
+
+        String adjType = null;
+        if (adj > clientAdj) {
+            if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+                    && clientAdj > PERCEPTIBLE_APP_ADJ) {
+                adjType = "cch-ui-provider";
+            } else {
+                adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
+                state.setCurRawAdj(adj);
+                adjType = "provider";
+            }
+            state.setCached(state.isCached() & cstate.isCached());
+        }
+
+        if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
+            if (adjType == null) {
+                adjType = "provider";
+            }
+            if (clientProcState == PROCESS_STATE_TOP) {
+                clientProcState = PROCESS_STATE_BOUND_TOP;
+            } else {
+                clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+            }
+        }
+
+        conn.trackProcState(clientProcState, mAdjSeq);
+        if (procState > clientProcState) {
+            procState = clientProcState;
+            state.setCurRawProcState(procState);
+        }
+        if (cstate.getCurrentSchedulingGroup() > schedGroup) {
+            schedGroup = SCHED_GROUP_DEFAULT;
+        }
+        if (adjType != null) {
+            state.setAdjType(adjType);
+            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                    .REASON_PROVIDER_IN_USE);
+            state.setAdjSource(client);
+            state.setAdjSourceProcState(clientProcState);
+            state.setAdjTarget(conn.provider.name);
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                        + ": " + app + ", due to " + client
+                        + " adj=" + adj + " procState="
+                        + ProcessList.makeProcStateString(procState));
+            }
+        }
+
+        // Procstates below BFGS should never have this capability.
+        if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+            capability &= ~PROCESS_CAPABILITY_BFSL;
+        }
+
+        if (adj < prevRawAdj) {
+            schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+        }
+        if (procState < prevProcState) {
+            setIntermediateProcStateLSP(app, procState, prevProcState);
+        }
+        if (schedGroup > prevSchedGroup) {
+            setIntermediateSchedGroupLSP(state, schedGroup);
+        }
+        state.setCurCapability(capability);
+
+        state.setEmpty(false);
+    }
+
+    protected int getDefaultCapability(ProcessRecord app, int procState) {
         final int networkCapabilities =
                 NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState);
         final int baseCapabilities;
@@ -2882,7 +3098,7 @@
     /**
      * @return the BFSL capability from a client (of a service binding or provider).
      */
-    int getBfslCapabilityFromClient(ProcessRecord client) {
+    protected int getBfslCapabilityFromClient(ProcessRecord client) {
         // Procstates above FGS should always have this flag. We shouldn't need this logic,
         // but let's do it just in case.
         if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -2967,7 +3183,7 @@
 
     /** Inform the oomadj observer of changes to oomadj. Used by tests. */
     @GuardedBy("mService")
-    private void reportOomAdjMessageLocked(String tag, String msg) {
+    protected void reportOomAdjMessageLocked(String tag, String msg) {
         Slog.d(tag, msg);
         synchronized (mService.mOomAdjObserverLock) {
             if (mService.mCurOomAdjObserver != null) {
@@ -2983,7 +3199,7 @@
 
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
     @GuardedBy({"mService", "mProcLock"})
-    private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
+    protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
             long nowElapsed, @OomAdjReason int oomAdjReson) {
         boolean success = true;
         final ProcessStateRecord state = app.mState;
@@ -3272,6 +3488,8 @@
         int initialCapability =  PROCESS_CAPABILITY_NONE;
         boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
+        final int prevProcState = PROCESS_STATE_UNKNOWN;
+        final int prevAdj = UNKNOWN_ADJ;
         // 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.
         if (state.hasForegroundActivities()) {
@@ -3306,6 +3524,9 @@
         state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
         state.setForcingToImportant(null);
         state.setHasShownUi(false);
+
+        onProcessStateChanged(app, prevProcState);
+        onProcessOomAdjChanged(app, prevAdj);
     }
 
     // ONLY used for unit testing in OomAdjusterTests.java
@@ -3553,4 +3774,56 @@
         }
         processes.clear();
     }
+
+    @GuardedBy("mService")
+    void onProcessBeginLocked(@NonNull ProcessRecord app) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @GuardedBy("mService")
+    void onProcessEndLocked(@NonNull ProcessRecord app) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    /**
+     * Called when the process state is changed outside of the OomAdjuster.
+     */
+    @GuardedBy("mService")
+    void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    /**
+     * Called when the oom adj is changed outside of the OomAdjuster.
+     */
+    @GuardedBy("mService")
+    void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @VisibleForTesting
+    void resetInternal() {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialAdj(@NonNull ProcessRecord app) {
+        return app.mState.getCurAdj();
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialProcState(@NonNull ProcessRecord app) {
+        return app.mState.getCurProcState();
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialCapability(@NonNull ProcessRecord app) {
+        return app.mState.getCurCapability();
+    }
+
+    @GuardedBy("mService")
+    protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+        // The caller will set the initial value in this implementation.
+        return app.mState.isCurBoundByNonBgRestrictedApp();
+    }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
index 16091d1..da5e12e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.md
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -130,3 +130,28 @@
   * Iterate the processes from least important to most important ones.
   * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3.
 
+## The Modern Implementation
+
+As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong.
+
+The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below:
+
+```
+for all processes in the process list
+  compute the state of each process, but, excluding its clients
+  put each process to the corresponding bucket according to the state value
+done
+
+for each bucket, starting from the top most to the bottom most
+  for each process in the bucket
+     for each process it binds to
+           if the state of the bindee process could be elevated because of the binding; then
+              move the bindee process to the higher bucket
+           fi
+      done
+  done
+done
+```
+
+The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm.
+
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
new file mode 100644
index 0000000..b852ef5
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -0,0 +1,1125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BACKUP;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+import static android.app.ActivityManager.PROCESS_STATE_HOME;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
+import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
+import static com.android.server.am.ProcessList.HOME_APP_ADJ;
+import static com.android.server.am.ProcessList.NATIVE_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
+import static com.android.server.am.ProcessList.SERVICE_ADJ;
+import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
+import static com.android.server.am.ProcessList.SYSTEM_ADJ;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * A modern implementation of the oom adjuster.
+ */
+public class OomAdjusterModernImpl extends OomAdjuster {
+    static final String TAG = "OomAdjusterModernImpl";
+
+    // The ADJ_SLOT_INVALID is NOT an actual slot.
+    static final int ADJ_SLOT_INVALID = -1;
+    static final int ADJ_SLOT_NATIVE = 0;
+    static final int ADJ_SLOT_SYSTEM = 1;
+    static final int ADJ_SLOT_PERSISTENT_PROC = 2;
+    static final int ADJ_SLOT_PERSISTENT_SERVICE = 3;
+    static final int ADJ_SLOT_FOREGROUND_APP = 4;
+    static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5;
+    static final int ADJ_SLOT_VISIBLE_APP = 6;
+    static final int ADJ_SLOT_PERCEPTIBLE_APP = 7;
+    static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8;
+    static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9;
+    static final int ADJ_SLOT_BACKUP_APP = 10;
+    static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11;
+    static final int ADJ_SLOT_SERVICE = 12;
+    static final int ADJ_SLOT_HOME_APP = 13;
+    static final int ADJ_SLOT_PREVIOUS_APP = 14;
+    static final int ADJ_SLOT_SERVICE_B = 15;
+    static final int ADJ_SLOT_CACHED_APP = 16;
+    static final int ADJ_SLOT_UNKNOWN = 17;
+
+    @IntDef(prefix = { "ADJ_SLOT_" }, value = {
+        ADJ_SLOT_INVALID,
+        ADJ_SLOT_NATIVE,
+        ADJ_SLOT_SYSTEM,
+        ADJ_SLOT_PERSISTENT_PROC,
+        ADJ_SLOT_PERSISTENT_SERVICE,
+        ADJ_SLOT_FOREGROUND_APP,
+        ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP,
+        ADJ_SLOT_VISIBLE_APP,
+        ADJ_SLOT_PERCEPTIBLE_APP,
+        ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP,
+        ADJ_SLOT_PERCEPTIBLE_LOW_APP,
+        ADJ_SLOT_BACKUP_APP,
+        ADJ_SLOT_HEAVY_WEIGHT_APP,
+        ADJ_SLOT_SERVICE,
+        ADJ_SLOT_HOME_APP,
+        ADJ_SLOT_PREVIOUS_APP,
+        ADJ_SLOT_SERVICE_B,
+        ADJ_SLOT_CACHED_APP,
+        ADJ_SLOT_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AdjSlot{}
+
+    static final int[] ADJ_SLOT_VALUES = new int[] {
+        NATIVE_ADJ,
+        SYSTEM_ADJ,
+        PERSISTENT_PROC_ADJ,
+        PERSISTENT_SERVICE_ADJ,
+        FOREGROUND_APP_ADJ,
+        PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ,
+        VISIBLE_APP_ADJ,
+        PERCEPTIBLE_APP_ADJ,
+        PERCEPTIBLE_MEDIUM_APP_ADJ,
+        PERCEPTIBLE_LOW_APP_ADJ,
+        BACKUP_APP_ADJ,
+        HEAVY_WEIGHT_APP_ADJ,
+        SERVICE_ADJ,
+        HOME_APP_ADJ,
+        PREVIOUS_APP_ADJ,
+        SERVICE_B_ADJ,
+        CACHED_APP_MIN_ADJ,
+        UNKNOWN_ADJ,
+    };
+
+    /**
+     * Note: Always use the raw adj to call this API.
+     */
+    static @AdjSlot int adjToSlot(int adj) {
+        if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) {
+            // Conduct a binary search, in most of the cases it'll get a hit.
+            final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj);
+            if (index >= 0) {
+                return index;
+            }
+            // If not found, the returned index above should be (-(insertion point) - 1),
+            // let's return the first slot that's less than the adj value.
+            return -(index + 1) - 1;
+        }
+        return ADJ_SLOT_VALUES.length - 1;
+    }
+
+    static final int[] PROC_STATE_SLOTS = new int[] {
+        PROCESS_STATE_PERSISTENT, // 0
+        PROCESS_STATE_PERSISTENT_UI,
+        PROCESS_STATE_TOP,
+        PROCESS_STATE_BOUND_TOP,
+        PROCESS_STATE_FOREGROUND_SERVICE,
+        PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+        PROCESS_STATE_IMPORTANT_FOREGROUND,
+        PROCESS_STATE_IMPORTANT_BACKGROUND,
+        PROCESS_STATE_TRANSIENT_BACKGROUND,
+        PROCESS_STATE_BACKUP,
+        PROCESS_STATE_SERVICE,
+        PROCESS_STATE_RECEIVER,
+        PROCESS_STATE_TOP_SLEEPING,
+        PROCESS_STATE_HEAVY_WEIGHT,
+        PROCESS_STATE_HOME,
+        PROCESS_STATE_LAST_ACTIVITY,
+        PROCESS_STATE_CACHED_ACTIVITY,
+        PROCESS_STATE_CACHED_ACTIVITY_CLIENT,
+        PROCESS_STATE_CACHED_RECENT,
+        PROCESS_STATE_CACHED_EMPTY,
+        PROCESS_STATE_UNKNOWN, // -1
+    };
+
+    static int processStateToSlot(@ActivityManager.ProcessState int state) {
+        if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) {
+            return state;
+        }
+        return PROC_STATE_SLOTS.length - 1;
+    }
+
+    /**
+     * A container node in the {@link LinkedProcessRecordList},
+     * holding the references to {@link ProcessRecord}.
+     */
+    static class ProcessRecordNode {
+        static final int NODE_TYPE_PROC_STATE = 0;
+        static final int NODE_TYPE_ADJ = 1;
+
+        @IntDef(prefix = { "NODE_TYPE_" }, value = {
+            NODE_TYPE_PROC_STATE,
+            NODE_TYPE_ADJ,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface NodeType {}
+
+        static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1;
+
+        @Nullable ProcessRecordNode mPrev;
+        @Nullable ProcessRecordNode mNext;
+        final @Nullable ProcessRecord mApp;
+
+        ProcessRecordNode(@Nullable ProcessRecord app) {
+            mApp = app;
+        }
+
+        void unlink() {
+            if (mPrev != null) {
+                mPrev.mNext = mNext;
+            }
+            if (mNext != null) {
+                mNext.mPrev = mPrev;
+            }
+            mPrev = mNext = null;
+        }
+
+        boolean isLinked() {
+            return mPrev != null && mNext != null;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("ProcessRecordNode{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(' ');
+            sb.append(mApp);
+            sb.append(' ');
+            sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN);
+            sb.append(' ');
+            sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ);
+            sb.append(' ');
+            sb.append(Integer.toHexString(System.identityHashCode(mPrev)));
+            sb.append(' ');
+            sb.append(Integer.toHexString(System.identityHashCode(mNext)));
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    private class ProcessRecordNodes {
+        private final @ProcessRecordNode.NodeType int mType;
+
+        private final LinkedProcessRecordList[] mProcessRecordNodes;
+        // The last node besides the tail.
+        private final ProcessRecordNode[] mLastNode;
+
+        ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
+            mType = type;
+            mProcessRecordNodes = new LinkedProcessRecordList[size];
+            for (int i = 0; i < size; i++) {
+                mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
+            }
+            mLastNode = new ProcessRecordNode[size];
+        }
+
+        int size() {
+            return mProcessRecordNodes.length;
+        }
+
+        @VisibleForTesting
+        void reset() {
+            for (int i = 0; i < mProcessRecordNodes.length; i++) {
+                mProcessRecordNodes[i].reset();
+                mLastNode[i] = null;
+            }
+        }
+
+        void resetLastNodes() {
+            for (int i = 0; i < mProcessRecordNodes.length; i++) {
+                mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
+            }
+        }
+
+        void setLastNodeToHead(int slot) {
+            mLastNode[slot] = mProcessRecordNodes[slot].HEAD;
+        }
+
+        void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
+            ProcessRecordNode node = mLastNode[slot].mNext;
+            final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
+            while (node != tail) {
+                mTmpOomAdjusterArgs.mApp = node.mApp;
+                // Save the next before calling callback, since that may change the node.mNext.
+                final ProcessRecordNode next = node.mNext;
+                callback.accept(mTmpOomAdjusterArgs);
+                // There are couple of cases:
+                // a) The current node is moved to another slot
+                //    - for this case, we'd need to keep using the "next" node.
+                // b) There are one or more new nodes being appended to this slot
+                //    - for this case, we'd need to make sure we scan the new node too.
+                // Based on the assumption that case a) is only possible with
+                // the computeInitialOomAdjLSP(), where the movings are for single node only,
+                // we may safely assume that, if the "next" used to be the "tail" here, and it's
+                // now a new tail somewhere else, that's case a); otherwise, it's case b);
+                node = next == tail && node.mNext != null && node.mNext.mNext != null
+                        ? node.mNext : next;
+            }
+        }
+
+        int getNumberOfSlots() {
+            return mProcessRecordNodes.length;
+        }
+
+        void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            if (prevSlot != ADJ_SLOT_INVALID) {
+                if (mLastNode[prevSlot] == node) {
+                    mLastNode[prevSlot] = node.mPrev;
+                }
+                node.unlink();
+            }
+            mProcessRecordNodes[newSlot].append(node);
+        }
+
+        void moveAllNodesTo(int fromSlot, int toSlot) {
+            final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot];
+            final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot];
+            if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) {
+                fromList.moveTo(toList);
+                mLastNode[fromSlot] = fromList.getLastNodeBeforeTail();
+            }
+        }
+
+        void moveAppToTail(ProcessRecord app) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            int slot;
+            switch (mType) {
+                case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+                    slot = processStateToSlot(app.mState.getCurProcState());
+                    if (mLastNode[slot] == node) {
+                        mLastNode[slot] = node.mPrev;
+                    }
+                    mProcessRecordNodes[slot].moveNodeToTail(node);
+                    break;
+                case ProcessRecordNode.NODE_TYPE_ADJ:
+                    slot = adjToSlot(app.mState.getCurRawAdj());
+                    if (mLastNode[slot] == node) {
+                        mLastNode[slot] = node.mPrev;
+                    }
+                    mProcessRecordNodes[slot].moveNodeToTail(node);
+                    break;
+                default:
+                    return;
+            }
+
+        }
+
+        void reset(int slot) {
+            mProcessRecordNodes[slot].reset();
+        }
+
+        void unlink(@NonNull ProcessRecord app) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            final int slot = getCurrentSlot(app);
+            if (slot != ADJ_SLOT_INVALID) {
+                if (mLastNode[slot] == node) {
+                    mLastNode[slot] = node.mPrev;
+                }
+            }
+            node.unlink();
+        }
+
+        void append(@NonNull ProcessRecord app) {
+            append(app, getCurrentSlot(app));
+        }
+
+        void append(@NonNull ProcessRecord app, int targetSlot) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            mProcessRecordNodes[targetSlot].append(node);
+        }
+
+        private int getCurrentSlot(@NonNull ProcessRecord app) {
+            switch (mType) {
+                case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+                    return processStateToSlot(app.mState.getCurProcState());
+                case ProcessRecordNode.NODE_TYPE_ADJ:
+                    return adjToSlot(app.mState.getCurRawAdj());
+            }
+            return ADJ_SLOT_INVALID;
+        }
+
+        String toString(int slot, int logUid) {
+            return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid);
+        }
+
+        /**
+         * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node
+         * while adding an object to it.
+         */
+        private static class LinkedProcessRecordList {
+            // Sentinel head/tail, to make bookkeeping work easier.
+            final ProcessRecordNode HEAD = new ProcessRecordNode(null);
+            final ProcessRecordNode TAIL = new ProcessRecordNode(null);
+            final @ProcessRecordNode.NodeType int mNodeType;
+
+            LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
+                HEAD.mNext = TAIL;
+                TAIL.mPrev = HEAD;
+                mNodeType = nodeType;
+            }
+
+            void append(@NonNull ProcessRecordNode node) {
+                node.mNext = TAIL;
+                node.mPrev = TAIL.mPrev;
+                TAIL.mPrev.mNext = node;
+                TAIL.mPrev = node;
+            }
+
+            void moveTo(@NonNull LinkedProcessRecordList toList) {
+                if (HEAD.mNext != TAIL) {
+                    toList.TAIL.mPrev.mNext = HEAD.mNext;
+                    HEAD.mNext.mPrev = toList.TAIL.mPrev;
+                    toList.TAIL.mPrev = TAIL.mPrev;
+                    TAIL.mPrev.mNext = toList.TAIL;
+                    HEAD.mNext = TAIL;
+                    TAIL.mPrev = HEAD;
+                }
+            }
+
+            void moveNodeToTail(@NonNull ProcessRecordNode node) {
+                node.unlink();
+                append(node);
+            }
+
+            @NonNull ProcessRecordNode getLastNodeBeforeTail() {
+                return TAIL.mPrev;
+            }
+
+            @VisibleForTesting
+            void reset() {
+                HEAD.mNext = TAIL;
+                TAIL.mPrev = HEAD;
+            }
+
+            String toString(int logUid) {
+                final StringBuilder sb = new StringBuilder();
+                sb.append("LinkedProcessRecordList{");
+                sb.append(HEAD);
+                sb.append(' ');
+                sb.append(TAIL);
+                sb.append('[');
+                ProcessRecordNode node = HEAD.mNext;
+                while (node != TAIL) {
+                    if (node.mApp != null && node.mApp.uid == logUid) {
+                        sb.append(node);
+                        sb.append(',');
+                    }
+                    node = node.mNext;
+                }
+                sb.append(']');
+                sb.append('}');
+                return sb.toString();
+            }
+        }
+    }
+
+    /**
+     * A data class for holding the parameters in computing oom adj.
+     */
+    private class OomAdjusterArgs {
+        ProcessRecord mApp;
+        ProcessRecord mTopApp;
+        long mNow;
+        int mCachedAdj;
+        @OomAdjReason int mOomAdjReason;
+        @NonNull ActiveUids mUids;
+        boolean mFullUpdate;
+
+        void update(ProcessRecord topApp, long now, int cachedAdj,
+                @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) {
+            mTopApp = topApp;
+            mNow = now;
+            mCachedAdj = cachedAdj;
+            mOomAdjReason = oomAdjReason;
+            mUids = uids;
+            mFullUpdate = fullUpdate;
+        }
+    }
+
+    OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+            ActiveUids activeUids) {
+        this(service, processList, activeUids, createAdjusterThread());
+    }
+
+    OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+            ActiveUids activeUids, ServiceThread adjusterThread) {
+        super(service, processList, activeUids, adjusterThread);
+    }
+
+    private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
+            ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length);
+    private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes(
+            ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length);
+    private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs();
+
+    void linkProcessRecordToList(@NonNull ProcessRecord app) {
+        mProcessRecordProcStateNodes.append(app);
+        mProcessRecordAdjNodes.append(app);
+    }
+
+    void unlinkProcessRecordFromList(@NonNull ProcessRecord app) {
+        mProcessRecordProcStateNodes.unlink(app);
+        mProcessRecordAdjNodes.unlink(app);
+    }
+
+    @Override
+    @VisibleForTesting
+    void resetInternal() {
+        mProcessRecordProcStateNodes.reset();
+        mProcessRecordAdjNodes.reset();
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessBeginLocked(@NonNull ProcessRecord app) {
+        // Check one type should be good enough.
+        if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) {
+            for (int i = 0; i < app.mLinkedNodes.length; i++) {
+                app.mLinkedNodes[i] = new ProcessRecordNode(app);
+            }
+        }
+        if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+            linkProcessRecordToList(app);
+        }
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessEndLocked(@NonNull ProcessRecord app) {
+        if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null
+                && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+            unlinkProcessRecordFromList(app);
+        }
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+        updateProcStateSlotIfNecessary(app, prevProcState);
+    }
+
+    @GuardedBy("mService")
+    void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+        updateAdjSlotIfNecessary(app, prevAdj);
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialAdj(@NonNull ProcessRecord app) {
+        return UNKNOWN_ADJ;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialProcState(@NonNull ProcessRecord app) {
+        return PROCESS_STATE_UNKNOWN;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialCapability(@NonNull ProcessRecord app) {
+        return 0;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+        return false;
+    }
+
+    private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
+        if (app.mState.getCurRawAdj() != prevRawAdj) {
+            final int slot = adjToSlot(app.mState.getCurRawAdj());
+            final int prevSlot = adjToSlot(prevRawAdj);
+            if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
+                mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+            }
+        }
+    }
+
+    private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
+        if (app.mState.getCurProcState() != prevProcState) {
+            final int slot = processStateToSlot(app.mState.getCurProcState());
+            final int prevSlot = processStateToSlot(prevProcState);
+            if (slot != prevSlot) {
+                mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+            }
+        }
+    }
+
+    @Override
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+        final ProcessRecord topApp = mService.getTopApp();
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+        mService.mOomAdjProfiler.oomAdjStarted();
+        mAdjSeq++;
+
+        final ProcessStateRecord state = app.mState;
+        final int oldAdj = state.getCurRawAdj();
+        final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
+                ? oldAdj : UNKNOWN_ADJ;
+
+        final ActiveUids uids = mTmpUidRecords;
+        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+        final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+
+        uids.clear();
+        targetProcesses.clear();
+        targetProcesses.add(app);
+        reachableProcesses.clear();
+
+        // Find out all reachable processes from this app.
+        collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
+
+        // Copy all of the reachable processes into the target process set.
+        targetProcesses.addAll(reachableProcesses);
+        reachableProcesses.clear();
+
+        final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
+                topApp, targetProcesses, uids, false, now, cachedAdj);
+
+        reachableProcesses.addAll(targetProcesses);
+        assignCachedAdjIfNecessary(reachableProcesses);
+        for (int  i = uids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = uids.valueAt(i);
+            uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+        }
+        updateUidsLSP(uids, nowElapsed);
+        for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+            applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+        }
+        targetProcesses.clear();
+        reachableProcesses.clear();
+
+        mService.mOomAdjProfiler.oomAdjEnded();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        return result;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+            ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
+            boolean startProfiling) {
+        final boolean fullUpdate = processes == null;
+        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+                ? mProcessList.getLruProcessesLOSP() : processes;
+        ActiveUids activeUids = uids;
+        if (activeUids == null) {
+            final int numUids = mActiveUids.size();
+            activeUids = mTmpUidRecords;
+            activeUids.clear();
+            for (int i = 0; i < numUids; i++) {
+                UidRecord uidRec = mActiveUids.valueAt(i);
+                activeUids.put(uidRec.getUid(), uidRec);
+            }
+        }
+
+        if (startProfiling) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+            mService.mOomAdjProfiler.oomAdjStarted();
+        }
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+        final int numProc = activeProcesses.size();
+
+        mAdjSeq++;
+        if (fullUpdate) {
+            mNewNumServiceProcs = 0;
+            mNewNumAServiceProcs = 0;
+        }
+
+        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+        targetProcesses.clear();
+        if (!fullUpdate) {
+            targetProcesses.addAll(activeProcesses);
+        }
+
+        performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
+                fullUpdate, now, UNKNOWN_ADJ);
+
+        if (fullUpdate) {
+            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+            postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+        } else {
+            activeProcesses.clear();
+            activeProcesses.addAll(targetProcesses);
+            assignCachedAdjIfNecessary(activeProcesses);
+
+            for (int  i = activeUids.size() - 1; i >= 0; i--) {
+                final UidRecord uidRec = activeUids.valueAt(i);
+                uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+            }
+            updateUidsLSP(activeUids, nowElapsed);
+
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+            }
+
+            activeProcesses.clear();
+        }
+        targetProcesses.clear();
+
+        if (startProfiling) {
+            mService.mOomAdjProfiler.oomAdjEnded();
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+        return;
+    }
+
+    /**
+     * Perform the oom adj update on the given {@code targetProcesses}.
+     *
+     * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
+     * must have called {@link collectReachableProcessesLocked} on it.
+     */
+    private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
+            ProcessRecord topApp,  ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
+            boolean fullUpdate, long now, int cachedAdj) {
+
+        final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
+        clientProcesses.clear();
+
+        // We'll need to collect the upstream processes of the target apps here, because those
+        // processes would potentially impact the procstate/adj via bindings.
+        if (!fullUpdate) {
+            final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses,
+                    clientProcesses);
+
+            // If any of its upstream processes are in a cycle,
+            // move them into the candidate targets.
+            if (containsCycle) {
+                // Add all client apps to the target process list.
+                for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+                    final ProcessRecord client = clientProcesses.get(i);
+                    final UidRecord uidRec = client.getUidRecord();
+                    targetProcesses.add(client);
+                    if (uidRec != null) {
+                        uids.put(uidRec.getUid(), uidRec);
+                    }
+                }
+                clientProcesses.clear();
+            }
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                final ProcessRecord app = targetProcesses.valueAt(i);
+                app.mState.resetCachedInfo();
+                final UidRecord uidRec = app.getUidRecord();
+                if (uidRec != null) {
+                    if (DEBUG_UID_OBSERVERS) {
+                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                    }
+                    uidRec.reset();
+                }
+            }
+        } else {
+            final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+            for (int i = 0, size = lru.size(); i < size; i++) {
+                final ProcessRecord app = lru.get(i);
+                app.mState.resetCachedInfo();
+                final UidRecord uidRec = app.getUidRecord();
+                if (uidRec != null) {
+                    if (DEBUG_UID_OBSERVERS) {
+                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                    }
+                    uidRec.reset();
+                }
+            }
+        }
+
+        updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
+                cachedAdj, now, fullUpdate);
+
+        clientProcesses.clear();
+
+        return true;
+    }
+
+    /**
+     * Collect the reversed reachable processes from the given {@code apps}, the result will be
+     * returned in the given {@code processes}, which will <em>NOT</em> include the processes from
+     * the given {@code apps}.
+     */
+    @GuardedBy("mService")
+    private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+            ArrayList<ProcessRecord> clientProcesses) {
+        final ArrayDeque<ProcessRecord> queue = mTmpQueue;
+        queue.clear();
+        clientProcesses.clear();
+        for (int i = 0, size = apps.size(); i < size; i++) {
+            final ProcessRecord app = apps.valueAt(i);
+            app.mState.setReachable(true);
+            app.mState.setReversedReachable(true);
+            queue.offer(app);
+        }
+
+        // Track if any of them reachables could include a cycle
+        boolean containsCycle = false;
+
+        // Scan upstreams of the process record
+        for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) {
+            if (!pr.mState.isReachable()) {
+                // If not in the given initial set of apps, add it.
+                clientProcesses.add(pr);
+            }
+            final ProcessServiceRecord psr = pr.mServices;
+            for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+                final ServiceRecord s = psr.getRunningServiceAt(i);
+                final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                        s.getConnections();
+                for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+                    final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+                    for (int k = clist.size() - 1; k >= 0; k--) {
+                        final ConnectionRecord cr = clist.get(k);
+                        final ProcessRecord client = cr.binding.client;
+                        containsCycle |= client.mState.isReversedReachable();
+                        if (client.mState.isReversedReachable()) {
+                            continue;
+                        }
+                        queue.offer(client);
+                        client.mState.setReversedReachable(true);
+                    }
+                }
+            }
+            final ProcessProviderRecord ppr = pr.mProviders;
+            for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+                final ContentProviderRecord cpr = ppr.getProviderAt(i);
+                for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+                    final ContentProviderConnection conn = cpr.connections.get(j);
+                    final ProcessRecord client = conn.client;
+                    containsCycle |= client.mState.isReversedReachable();
+                    if (client.mState.isReversedReachable()) {
+                        continue;
+                    }
+                    queue.offer(client);
+                    client.mState.setReversedReachable(true);
+                }
+            }
+            // If this process is a sandbox itself, also add the app on whose behalf
+            // its running
+            if (pr.isSdkSandbox) {
+                for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
+                    ServiceRecord s = psr.getRunningServiceAt(is);
+                    ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                            s.getConnections();
+                    for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+                        ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+                        for (int i = clist.size() - 1; i >= 0; i--) {
+                            ConnectionRecord cr = clist.get(i);
+                            ProcessRecord attributedApp = cr.binding.attributedClient;
+                            if (attributedApp == null || attributedApp == pr) {
+                                continue;
+                            }
+                            containsCycle |= attributedApp.mState.isReversedReachable();
+                            if (attributedApp.mState.isReversedReachable()) {
+                                continue;
+                            }
+                            queue.offer(attributedApp);
+                            attributedApp.mState.setReversedReachable(true);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Reset the temporary bits.
+        for (int i = clientProcesses.size() - 1; i >= 0; i--) {
+            clientProcesses.get(i).mState.setReversedReachable(false);
+        }
+        for (int i = 0, size = apps.size(); i < size; i++) {
+            final ProcessRecord app = apps.valueAt(i);
+            app.mState.setReachable(false);
+            app.mState.setReversedReachable(false);
+        }
+        return containsCycle;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+            ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
+            ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
+        mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
+
+        mProcessRecordProcStateNodes.resetLastNodes();
+        mProcessRecordAdjNodes.resetLastNodes();
+
+        final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
+        final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+
+        final int appUid = !fullUpdate && targetProcesses.size() > 0
+                ? targetProcesses.valueAt(0).uid : -1;
+        final int logUid = mService.mCurOomAdjUid;
+
+        mAdjSeq++;
+        // All apps to be updated will be moved to the lowest slot.
+        if (fullUpdate) {
+            // Move all the process record node to the lowest slot, we'll do recomputation on all of
+            // them. Use the processes from the lru list, because the scanning order matters here.
+            final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
+            for (int i = procStateTarget; i >= 0; i--) {
+                mProcessRecordProcStateNodes.reset(i);
+                // Force the last node to the head since we'll recompute all of them.
+                mProcessRecordProcStateNodes.setLastNodeToHead(i);
+            }
+            // enqueue the targets in the reverse order of the lru list.
+            for (int i = lruList.size() - 1; i >= 0; i--) {
+                mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
+            }
+            // Do the same to the adj nodes.
+            for (int i = adjTarget; i >= 0; i--) {
+                mProcessRecordAdjNodes.reset(i);
+                // Force the last node to the head since we'll recompute all of them.
+                mProcessRecordAdjNodes.setLastNodeToHead(i);
+            }
+            for (int i = lruList.size() - 1; i >= 0; i--) {
+                mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
+            }
+        } else {
+            // Move the target processes to the lowest slot.
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                final ProcessRecord app = targetProcesses.valueAt(i);
+                final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
+                final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
+                mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
+                mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
+            }
+            // Move the "lastNode" to head to make sure we scan all nodes in this slot.
+            mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
+            mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+        }
+
+        // All apps to be updated have been moved to the lowest slot.
+        // Do an initial pass of the computation.
+        mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
+                this::computeInitialOomAdjLSP);
+
+        if (!fullUpdate) {
+            // We didn't update the client processes with the computeInitialOomAdjLSP
+            // because they don't need to do so. But they'll be playing vital roles in
+            // computing the bindings. So include them into the scan list below.
+            for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+                mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
+            }
+            // We don't update the adj list since we're resetting it below.
+        }
+
+        // Now nodes are set into their slots, without facting in the bindings.
+        // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
+        //
+        // The whole rationale here is that, the bindings from client to host app, won't elevate
+        // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
+        // is a special case here, but client app's raw adj is still no less than the host app's).
+        // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
+        // check its bindings, elevate its host app's slot if necessary.
+        //
+        // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
+        // Because the procstate and adj are not always in sync - there are cases where
+        // the processes with lower proc state could be getting a higher oom adj score.
+        // And because of this, the procstate and adj node lists are basically two priority heaps.
+        //
+        // As the 2nd pass with the adj node lists potentially includes a significant amount of
+        // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
+        // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
+        // gets bumped, we'll only scan those in 2nd pass.
+
+        mProcessRecordAdjNodes.resetLastNodes();
+
+        // 1st pass, scan each slot in the procstate node list.
+        for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+            mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+        }
+
+        // 2nd pass, scan each slot in the adj node list.
+        for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+            mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
+        final ProcessRecord app = args.mApp;
+        final int cachedAdj = args.mCachedAdj;
+        final ProcessRecord topApp = args.mTopApp;
+        final long now = args.mNow;
+        final int oomAdjReason = args.mOomAdjReason;
+        final ActiveUids uids = args.mUids;
+        final boolean fullUpdate = args.mFullUpdate;
+
+        if (DEBUG_OOM_ADJ) {
+            Slog.i(TAG, "OOM ADJ initial args app=" + app
+                    + " cachedAdj=" + cachedAdj
+                    + " topApp=" + topApp
+                    + " now=" + now
+                    + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
+                    + " fullUpdate=" + fullUpdate);
+        }
+
+        if (uids != null) {
+            final UidRecord uidRec = app.getUidRecord();
+
+            if (uidRec != null) {
+                uids.put(uidRec.getUid(), uidRec);
+            }
+        }
+
+        computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
+                false);
+    }
+
+    /**
+     * @return The proposed change to the schedGroup.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+            int schedGroup) {
+        schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+
+        updateAdjSlotIfNecessary(app, prevRawAppAdj);
+
+        return schedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+            int prevProcState) {
+        super.setIntermediateProcStateLSP(app, procState, prevProcState);
+
+        updateProcStateSlotIfNecessary(app, prevProcState);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+        final ProcessRecord app = args.mApp;
+        final int cachedAdj = args.mCachedAdj;
+        final ProcessRecord topApp = args.mTopApp;
+        final long now = args.mNow;
+        final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+        final boolean fullUpdate = args.mFullUpdate;
+        final ActiveUids uids = args.mUids;
+
+        final ProcessServiceRecord psr = app.mServices;
+        for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+            ConnectionRecord cr = psr.getConnectionAt(i);
+            ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS)
+                    ? cr.binding.service.isolationHostProc : cr.binding.service.app;
+            if (service == null || service == app
+                    || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+
+            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+
+        for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
+            final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i);
+            final ProcessRecord service = cr.binding.service.app;
+            if (service == null || service == app
+                    || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
+            ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
+            ProcessRecord provider = cpc.provider.proc;
+            if (provider == null || provider == app
+                    || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
+                            && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+            computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f532122c1..7037fec 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -63,6 +64,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.Zygote;
 import com.android.server.FgThread;
+import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode;
 import com.android.server.wm.WindowProcessController;
 import com.android.server.wm.WindowProcessListener;
 
@@ -434,6 +436,8 @@
      */
     volatile boolean mSkipProcessGroupCreation;
 
+    final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startUptime, long startElapsedTime) {
         this.mStartUid = startUid;
@@ -1114,6 +1118,7 @@
         mState.onCleanupApplicationRecordLSP();
         mServices.onCleanupApplicationRecordLocked();
         mReceivers.onCleanupApplicationRecordLocked();
+        mService.mOomAdjuster.onProcessEndLocked(this);
 
         return mProviders.onCleanupApplicationRecordLocked(allowRestart);
     }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 7ff6d11..a165e88 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -19,6 +19,7 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
@@ -134,6 +135,11 @@
     private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
 
     /**
+     * All ConnectionRecord this process holds indirectly to SDK sandbox processes.
+     */
+    private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections;
+
+    /**
      * A set of UIDs of all bound clients.
      */
     private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
@@ -490,13 +496,18 @@
 
     void addConnection(ConnectionRecord connection) {
         mConnections.add(connection);
+        addSdkSandboxConnectionIfNecessary(connection);
     }
 
     void removeConnection(ConnectionRecord connection) {
         mConnections.remove(connection);
+        removeSdkSandboxConnectionIfNecessary(connection);
     }
 
     void removeAllConnections() {
+        for (int i = 0, size = mConnections.size(); i < size; i++) {
+            removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i));
+        }
         mConnections.clear();
     }
 
@@ -508,6 +519,39 @@
         return mConnections.size();
     }
 
+    private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+        final ProcessRecord attributedClient = connection.binding.attributedClient;
+        if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+                attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>();
+            }
+            attributedClient.mServices.mSdkSandboxConnections.add(connection);
+        }
+    }
+
+    private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+        final ProcessRecord attributedClient = connection.binding.attributedClient;
+        if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+                attributedClient.mServices.mSdkSandboxConnections.remove(connection);
+            }
+        }
+    }
+
+    void removeAllSdkSandboxConnections() {
+        if (mSdkSandboxConnections != null) {
+            mSdkSandboxConnections.clear();
+        }
+    }
+
+    ConnectionRecord getSdkSandboxConnectionAt(int index) {
+        return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null;
+    }
+
+    int numberOfSdkSandboxConnections() {
+        return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0;
+    }
+
     void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) {
         mBoundClientUids.add(clientUid);
         mApp.getWindowProcessController()
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index db341d2..a9c388c 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -378,6 +378,12 @@
     private boolean mReachable;
 
     /**
+     * Whether or not this process is reversed reachable from given process.
+     */
+    @GuardedBy("mService")
+    private boolean mReversedReachable;
+
+    /**
      * The most recent time when the last visible activity within this process became invisible.
      *
      * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
@@ -454,6 +460,9 @@
     @GuardedBy("mService")
     private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
 
+    @GuardedBy("mService")
+    private boolean mScheduleLikeTopApp = false;
+
     ProcessStateRecord(ProcessRecord app) {
         mApp = app;
         mService = app.mService;
@@ -614,9 +623,11 @@
     void forceProcessStateUpTo(int newState) {
         if (mRepProcState > newState) {
             synchronized (mProcLock) {
+                final int prevProcState = mRepProcState;
                 setReportedProcState(newState);
                 setCurProcState(newState);
                 setCurRawProcState(newState);
+                mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState);
             }
         }
     }
@@ -985,6 +996,16 @@
     }
 
     @GuardedBy("mService")
+    boolean isReversedReachable() {
+        return mReversedReachable;
+    }
+
+    @GuardedBy("mService")
+    void setReversedReachable(boolean reversedReachable) {
+        mReversedReachable = reversedReachable;
+    }
+
+    @GuardedBy("mService")
     void resetCachedInfo() {
         mCachedHasActivities = VALUE_INVALID;
         mCachedIsHeavyWeight = VALUE_INVALID;
@@ -1134,6 +1155,16 @@
         return mCachedSchedGroup;
     }
 
+    @GuardedBy("mService")
+    boolean shouldScheduleLikeTopApp() {
+        return mScheduleLikeTopApp;
+    }
+
+    @GuardedBy("mService")
+    void setScheduleLikeTopApp(boolean scheduleLikeTopApp) {
+        mScheduleLikeTopApp = scheduleLikeTopApp;
+    }
+
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     public String makeAdjReason() {
         if (mAdjSource != null || mAdjTarget != null) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index c6d6122..80d14a2 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -346,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);
@@ -369,6 +372,7 @@
                     break;
                 }
                 case CANCEL_GAME_LOADING_MODE: {
+                    Slog.v(TAG, "Game loading power mode OFF (loading boost ended)");
                     mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
                     break;
                 }
@@ -1279,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);
             }
 
@@ -1555,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/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 5819ff0..5c1897d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -185,8 +185,7 @@
      * True if {@link #mWakeLock} is open for acquisition. It is set to false after the client is
      * unregistered.
      */
-    @GuardedBy("mWakeLock")
-    private boolean mIsWakelockUsable = true;
+    private AtomicBoolean mIsWakelockUsable = new AtomicBoolean(true);
 
     /*
      * Internal interface used to invoke client callbacks.
@@ -529,7 +528,7 @@
     @VisibleForTesting
     boolean isWakelockUsable() {
         synchronized (mWakeLock) {
-            return mIsWakelockUsable;
+            return mIsWakelockUsable.get();
         }
     }
 
@@ -1103,10 +1102,8 @@
     private void acquireWakeLock() {
         Binder.withCleanCallingIdentity(
                 () -> {
-                    synchronized (mWakeLock) {
-                        if (mIsWakelockUsable) {
-                            mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
-                        }
+                    if (mIsWakelockUsable.get()) {
+                        mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
                     }
                 });
     }
@@ -1119,13 +1116,11 @@
     private void releaseWakeLock() {
         Binder.withCleanCallingIdentity(
                 () -> {
-                    synchronized (mWakeLock) {
-                        if (mWakeLock.isHeld()) {
-                            try {
-                                mWakeLock.release();
-                            } catch (RuntimeException e) {
-                                Log.e(TAG, "Releasing the wakelock fails - ", e);
-                            }
+                    if (mWakeLock.isHeld()) {
+                        try {
+                            mWakeLock.release();
+                        } catch (RuntimeException e) {
+                            Log.e(TAG, "Releasing the wakelock fails - ", e);
                         }
                     }
                 });
@@ -1139,18 +1134,16 @@
     private void releaseWakeLockOnExit() {
         Binder.withCleanCallingIdentity(
                 () -> {
-                    synchronized (mWakeLock) {
-                        mIsWakelockUsable = false;
-                        while (mWakeLock.isHeld()) {
-                            try {
-                                mWakeLock.release();
-                            } catch (RuntimeException e) {
-                                Log.e(
-                                        TAG,
-                                        "Releasing the wakelock for all acquisitions fails - ",
-                                        e);
-                                break;
-                            }
+                    mIsWakelockUsable.set(false);
+                    while (mWakeLock.isHeld()) {
+                        try {
+                            mWakeLock.release();
+                        } catch (RuntimeException e) {
+                            Log.e(
+                                    TAG,
+                                    "Releasing the wakelock for all acquisitions fails - ",
+                                    e);
+                            break;
                         }
                     }
                 });
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 09f373f..35c6120 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -466,8 +466,9 @@
                 // TODO(271471342): Implement
             }
 
-            public byte[] getUuid() {
-                return UUID;
+            public byte[] getUuid() throws RemoteException {
+                //TODO(b/247124878): return the UUID defined in this file when the API is put in use
+                throw new RemoteException("This API is not implemented yet.");
             }
 
             @Override
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index ec0d985..77a60289 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -123,7 +123,7 @@
      * @param userId The uid of the user whose profile has been unlocked.
      * @param credentialType The type of credential as defined in {@code LockPatternUtils}
      * @param credential The credential, encoded as a byte array
-     * @param credentialUpdated signals weather credentials were updated.
+     * @param credentialUpdated indicates credentials change.
      * @param platformKeyManager platform key manager
      * @param testOnlyInsecureCertificateHelper utility class used for end-to-end tests
      */
@@ -143,7 +143,7 @@
         mRecoverableKeyStoreDb = recoverableKeyStoreDb;
         mUserId = userId;
         mCredentialType = credentialType;
-        mCredential = credential;
+        mCredential = credential != null ? Arrays.copyOf(credential, credential.length) : null;
         mCredentialUpdated = credentialUpdated;
         mPlatformKeyManager = platformKeyManager;
         mRecoverySnapshotStorage = snapshotStorage;
@@ -160,6 +160,10 @@
             }
         } catch (Exception e) {
             Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e);
+        } finally {
+            if (mCredential != null) {
+                Arrays.fill(mCredential, (byte) 0); // no longer needed.
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index b5c0417..1ed829e4 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -32,6 +32,7 @@
 import android.content.pm.UserInfo;
 import android.content.pm.UserProperties;
 import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.Slog;
@@ -421,9 +422,19 @@
         } else {
             candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
         }
+        // When we have only a single result belonging to a different user space, we check
+        // if filtering is configured on cross-profile intent resolution for the originating user.
+        // Accordingly, we modify the intent to reflect the content-owner as the originating user,
+        // preventing the resolving user to be assumed the same, when activity is started.
+
+        // In case more than one result is present, the resolver sheet is opened which takes care of
+        // cross user access.
+        if (candidates.size() == 1 && !UserHandle.of(userId).equals(candidates.get(0).userHandle)
+                && isNoFilteringPropertyConfiguredForUser(userId)) {
+            intent.prepareToLeaveUser(userId);
+        }
         return new QueryIntentActivitiesResult(sortResult, addInstant, candidates);
     }
-
     /**
      * It filters and combines results from current and cross profile based on domain priority.
      * @param computer {@link Computer} instance
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 80e07f4..10cd51a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1886,7 +1886,14 @@
         }
 
         public void onSessionFinished(final PackageInstallerSession session, boolean success) {
-            mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
+            if (success) {
+                // There is a timing issue here, if the callback opens the session again in
+                // notifySessionFinished() immediately, the session may not be removed from
+                // the mSession. But to avoid adding unknown latency, only notifying failures
+                // are moved to the last of posted runnable, notifying success cases are
+                // still kept here.
+                mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
+            }
 
             mInstallHandler.post(new Runnable() {
                 @Override
@@ -1915,6 +1922,10 @@
 
                         mSettingsWriteRequest.runNow();
                     }
+                    if (!success) {
+                        mCallbacks.notifySessionFinished(
+                                session.sessionId, session.userId, success);
+                    }
                 }
             });
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 710e0b7..dd434fbe 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -37,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;
@@ -1927,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);
     }
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index f0bf1ea8..d0c346a 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -334,7 +334,10 @@
                             ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
                     cancellationReason,
                     durationMs,
-                    0);  // deprecated, used to be durationIncludingSleepMs
+                    0, // deprecated, used to be durationIncludingSleepMs
+                    0, // optimizedPackagesCount
+                    0, // packagesDependingOnBootClasspathCount
+                    0); // totalPackagesCount
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index b01a89e..7897195 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -72,6 +72,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
@@ -93,7 +94,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.BiFunction;
 
 /**
  * Manages all permissions and handles permissions related tasks.
@@ -233,11 +233,11 @@
         }
 
         if (checkPermissionDelegate == null) {
-            return mPermissionManagerServiceImpl.checkPermission(
-                    packageName, permissionName, userId);
+            return mPermissionManagerServiceImpl.checkPermission(packageName, permissionName,
+                    deviceId, userId);
         }
-        return checkPermissionDelegate.checkPermission(packageName, permissionName, userId,
-                mPermissionManagerServiceImpl::checkPermission);
+        return checkPermissionDelegate.checkPermission(packageName, permissionName,
+                deviceId, userId, mPermissionManagerServiceImpl::checkPermission);
     }
 
     @Override
@@ -254,10 +254,10 @@
         }
 
         if (checkPermissionDelegate == null)  {
-            return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName);
+            return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId);
         }
         return checkPermissionDelegate.checkUidPermission(uid, permissionName,
-                mPermissionManagerServiceImpl::checkUidPermission);
+                deviceId, mPermissionManagerServiceImpl::checkUidPermission);
     }
 
     @Override
@@ -511,14 +511,14 @@
     public int getPermissionFlags(String packageName, String permissionName, int deviceId,
             int userId) {
         return mPermissionManagerServiceImpl
-                .getPermissionFlags(packageName, permissionName, userId);
+                .getPermissionFlags(packageName, permissionName, deviceId, userId);
     }
 
     @Override
     public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
             int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
-                flagValues, checkAdjustPolicyFlagPermission, userId);
+                flagValues, checkAdjustPolicyFlagPermission, deviceId, userId);
     }
 
     @Override
@@ -560,14 +560,15 @@
     @Override
     public void grantRuntimePermission(String packageName, String permissionName, int deviceId,
             int userId) {
-        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
+        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName,
+                deviceId, userId);
     }
 
     @Override
     public void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
             int userId, String reason) {
         mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName,
-                userId, reason);
+                deviceId, userId, reason);
     }
 
     @Override
@@ -580,14 +581,14 @@
     public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
             int deviceId, int userId) {
         return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
-                permissionName, userId);
+                permissionName, deviceId, userId);
     }
 
     @Override
     public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
             int deviceId, int userId) {
-        return mPermissionManagerServiceImpl
-                .isPermissionRevokedByPolicy(packageName, permissionName, userId);
+        return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName,
+                permissionName, deviceId, userId);
     }
 
     @Override
@@ -868,6 +869,7 @@
          *
          * @param packageName the name of the package to be checked
          * @param permissionName the name of the permission to be checked
+         * @param deviceId The device ID
          * @param userId the user ID
          * @param superImpl the original implementation that can be delegated to
          * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
@@ -876,20 +878,21 @@
          * @see android.content.pm.PackageManager#checkPermission(String, String)
          */
         int checkPermission(@NonNull String packageName, @NonNull String permissionName,
-                @UserIdInt int userId,
-                @NonNull TriFunction<String, String, Integer, Integer> superImpl);
+                int deviceId, @UserIdInt int userId,
+                @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl);
 
         /**
          * Check whether the given UID has been granted the specified permission.
          *
          * @param uid the UID to be checked
          * @param permissionName the name of the permission to be checked
+         * @param deviceId The device ID
          * @param superImpl the original implementation that can be delegated to
          * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
          * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
          */
-        int checkUidPermission(int uid, @NonNull String permissionName,
-                BiFunction<Integer, String, Integer> superImpl);
+        int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
+                TriFunction<Integer, String, Integer, Integer> superImpl);
 
         /**
          * @return list of delegated permissions
@@ -918,31 +921,32 @@
 
         @Override
         public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
-                int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) {
+                int deviceId, int userId,
+                @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl) {
             if (mDelegatedPackageName.equals(packageName)
                     && isDelegatedPermission(permissionName)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply("com.android.shell", permissionName, userId);
+                    return superImpl.apply("com.android.shell", permissionName, deviceId, userId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(packageName, permissionName, userId);
+            return superImpl.apply(packageName, permissionName, deviceId, userId);
         }
 
         @Override
-        public int checkUidPermission(int uid, @NonNull String permissionName,
-                @NonNull BiFunction<Integer, String, Integer> superImpl) {
+        public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
+                @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) {
             if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(Process.SHELL_UID, permissionName);
+                    return superImpl.apply(Process.SHELL_UID, permissionName, deviceId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(uid, permissionName);
+            return superImpl.apply(uid, permissionName, deviceId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 4353c57..6764e08 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -681,7 +681,7 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
         final int callingUid = Binder.getCallingUid();
         return getPermissionFlagsInternal(packageName, permName, callingUid, userId);
     }
@@ -724,7 +724,7 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         final int callingUid = Binder.getCallingUid();
         boolean overridePolicy = false;
 
@@ -908,8 +908,12 @@
         }
     }
 
+    private int checkPermission(String pkgName, String permName, int userId) {
+        return checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT, userId);
+    }
+
     @Override
-    public int checkPermission(String pkgName, String permName, int userId) {
+    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
         if (!mUserManagerInt.exists(userId)) {
             return PackageManager.PERMISSION_DENIED;
         }
@@ -975,8 +979,12 @@
         return true;
     }
 
+    private int checkUidPermission(int uid, String permName) {
+        return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT);
+    }
+
     @Override
-    public int checkUidPermission(int uid, String permName) {
+    public int checkUidPermission(int uid, String permName, int deviceId) {
         final int userId = UserHandle.getUserId(uid);
         if (!mUserManagerInt.exists(userId)) {
             return PackageManager.PERMISSION_DENIED;
@@ -1295,7 +1303,8 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+    public void grantRuntimePermission(String packageName, String permName, int deviceId,
+            int userId) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
                 checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
@@ -1468,11 +1477,11 @@
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+            int userId, String reason) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
-                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
+                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, deviceId)
                         == PackageManager.PERMISSION_GRANTED;
 
         revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId,
@@ -1859,7 +1868,7 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            @UserIdInt int userId) {
+            int deviceId, @UserIdInt int userId) {
         final int callingUid = Binder.getCallingUid();
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(
@@ -1922,7 +1931,8 @@
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+            int userId) {
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -2059,8 +2069,8 @@
                     continue;
                 }
                 boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(),
-                        permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED
-                        | FLAG_PERMISSION_POLICY_FIXED)) != 0;
+                        permInfo.name, Context.DEVICE_ID_DEFAULT, userId) & (
+                        FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED)) != 0;
                 if (isSystemOrPolicyFixed) {
                     continue;
                 }
@@ -2226,7 +2236,8 @@
                 for (final int userId : userIds) {
                     final int permissionState = checkPermission(packageName, permName,
                             userId);
-                    final int flags = getPermissionFlags(packageName, permName, userId);
+                    final int flags = getPermissionFlags(packageName, permName,
+                            Context.DEVICE_ID_DEFAULT, userId);
                     final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
                             | FLAG_PERMISSION_POLICY_FIXED
                             | FLAG_PERMISSION_GRANTED_BY_DEFAULT
@@ -5122,8 +5133,7 @@
 
     @NonNull
     @Override
-    public Set<String> getGrantedPermissions(@NonNull String packageName,
-            @UserIdInt int userId) {
+    public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
         Objects.requireNonNull(packageName, "packageName");
         Preconditions.checkArgumentNonNegative(userId, "userId");
         return getGrantedPermissionsInternal(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 128f847..2d824aa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -25,7 +25,6 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
-import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
 
 import com.android.server.pm.pkg.AndroidPackage;
@@ -137,14 +136,16 @@
     void removePermission(String permName);
 
     /**
-     * Gets the state flags associated with a permission.
+     * Gets the permission state flags associated with a permission.
      *
      * @param packageName the package name for which to get the flags
      * @param permName the permission for which to get the flags
+     * @param deviceId The device for which to get the flags
      * @param userId the user for which to get permission flags
      * @return the permission flags
      */
-    int getPermissionFlags(String packageName, String permName, int userId);
+    int getPermissionFlags(String packageName, String permName, int deviceId,
+            @UserIdInt int userId);
 
     /**
      * Updates the flags associated with a permission by replacing the flags in the specified mask
@@ -154,10 +155,11 @@
      * @param permName The permission for which to update the flags
      * @param flagMask The flags which to replace
      * @param flagValues The flags with which to replace
+     * @param deviceId The device for which to update the permission flags
      * @param userId The user for which to update the permission flags
      */
-    void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+    void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues,
+            boolean checkAdjustPolicyFlagPermission, int deviceId, @UserIdInt int userId);
 
     /**
      * Update the permission flags for all packages and runtime permissions of a user in order
@@ -291,11 +293,13 @@
      *
      * @param packageName the package to which to grant the permission
      * @param permName the permission name to grant
+     * @param deviceId the device for which to grant the permission
      * @param userId the user for which to grant the permission
      *
-     * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String)
+     * @see #revokeRuntimePermission(String, String, int, int, String)
      */
-    void grantRuntimePermission(String packageName, String permName, int userId);
+    void grantRuntimePermission(String packageName, String permName, int deviceId,
+            @UserIdInt int userId);
 
     /**
      * Revoke a runtime permission that was previously granted by
@@ -310,13 +314,14 @@
      *
      * @param packageName the package from which to revoke the permission
      * @param permName the permission name to revoke
+     * @param deviceId the device for which to revoke the permission
      * @param userId the user for which to revoke the permission
      * @param reason the reason for the revoke, or {@code null} for unspecified
      *
-     * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+     * @see #grantRuntimePermission(String, String, int, int)
      */
-    void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason);
+    void revokeRuntimePermission(String packageName, String permName, int deviceId,
+            @UserIdInt int userId, String reason);
 
     /**
      * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
@@ -333,24 +338,29 @@
      * does not clearly communicate to the user what would be the benefit from grating this
      * permission.
      *
+     * @param packageName the package name
      * @param permName a permission your app wants to request
+     * @param deviceId the device for which to check the permission
+     * @param userId the user for which to check the permission
      * @return whether you can show permission rationale UI
      */
     boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            @UserIdInt int userId);
+            int deviceId, @UserIdInt int userId);
 
     /**
-     * Checks whether a particular permissions has been revoked for a package by policy. Typically
+     * Checks whether a particular permission has been revoked for a package by policy. Typically,
      * the device owner or the profile owner may apply such a policy. The user cannot grant policy
      * revoked permissions, hence the only way for an app to get such a permission is by a policy
      * change.
      *
      * @param packageName the name of the package you are checking against
      * @param permName the name of the permission you are checking for
-     *
+     * @param deviceId the device for which you are checking the permission
+     * @param userId the device for which you are checking the permission
      * @return whether the permission is restricted by policy
      */
-    boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId);
+    boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+            @UserIdInt int userId);
 
     /**
      * Get set of permissions that have been split into more granular or dependent permissions.
@@ -373,14 +383,25 @@
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
     /**
-     * TODO:theianchen add doc describing this is the old checkPermissionImpl
+     * Check whether a permission is granted or not to a package.
+     *
+     * @param pkgName package name
+     * @param permName permission name
+     * @param deviceId device ID
+     * @param userId user ID
+     * @return permission result {@link PackageManager.PermissionResult}
      */
-    int checkPermission(String pkgName, String permName, int userId);
+    int checkPermission(String pkgName, String permName, int deviceId, @UserIdInt int userId);
 
     /**
-     * TODO:theianchen add doc describing this is the old checkUidPermissionImpl
+     * Check whether a permission is granted or not to an UID.
+     *
+     * @param uid UID
+     * @param permName permission name
+     * @param deviceId device ID
+     * @return permission result {@link PackageManager.PermissionResult}
      */
-    int checkUidPermission(int uid, String permName);
+    int checkUidPermission(int uid, String permName, int deviceId);
 
     /**
      * Get all the package names requesting app op permissions.
@@ -400,15 +421,11 @@
             @UserIdInt int userId);
 
     /**
-     * Reset the runtime permission state changes for a package.
+     * Reset the runtime permission state changes for a package for all devices.
      *
      * TODO(zhanghai): Turn this into package change callback?
-     *
-     * @param pkg the package
-     * @param userId the user ID
      */
-    void resetRuntimePermissions(@NonNull AndroidPackage pkg,
-            @UserIdInt int userId);
+    void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId);
 
     /**
      * Reset the runtime permission state changes for all packages in a user.
@@ -449,8 +466,8 @@
     /**
      * Get all the permissions granted to a package.
      *
-     * @param packageName the name of the package
-     * @param userId the user ID
+     * @param packageName package name
+     * @param userId user ID
      * @return the names of the granted permissions
      */
     @NonNull
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index 7f98e21..dacb8c6 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -120,21 +120,21 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
         Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = "
-                + permName + ", userId = " + userId + ")");
-        return mService.getPermissionFlags(packageName, permName, userId);
+                + permName + ", deviceId = " + deviceId +  ", userId = " + userId + ")");
+        return mService.getPermissionFlags(packageName, permName, deviceId, userId);
     }
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = "
                 + permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues
                 + ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission
-                + ", userId = " + userId + ")");
+                + ", deviceId = " + deviceId + ", userId = " + userId + ")");
         mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, userId);
+                checkAdjustPolicyFlagPermission, deviceId, userId);
     }
 
     @Override
@@ -182,18 +182,20 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int userId) {
+    public void grantRuntimePermission(String packageName, String permName, int deviceId,
+            int userId) {
         Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = "
-                + permName + ", userId = " + userId + ")");
-        mService.grantRuntimePermission(packageName, permName, userId);
+                + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+        mService.grantRuntimePermission(packageName, permName, deviceId, userId);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+            int userId, String reason) {
         Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = "
-                + permName + ", userId = " + userId + ", reason = " + reason + ")");
-        mService.revokeRuntimePermission(packageName, permName, userId, reason);
+                + permName + ", deviceId = " + deviceId + ", userId = " + userId
+                + ", reason = " + reason + ")");
+        mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
     }
 
     @Override
@@ -205,17 +207,20 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int userId) {
+            int deviceId, int userId) {
         Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName
-                + ", permName = " + permName + ", userId = " + userId + ")");
-        return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
+                + ", permName = " + permName + ", deviceId = " + deviceId
+                +  ", userId = " + userId + ")");
+        return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId,
+                userId);
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+            int userId) {
         Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = "
-                + permName + ", userId = " + userId + ")");
-        return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
+                + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+        return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
     }
 
     @Override
@@ -225,16 +230,17 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int userId) {
+    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
         Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName
-                + ", userId = " + userId + ")");
-        return mService.checkPermission(pkgName, permName, userId);
+                + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+        return mService.checkPermission(pkgName, permName, deviceId, userId);
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName) {
-        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + ")");
-        return mService.checkUidPermission(uid, permName);
+    public int checkUidPermission(int uid, String permName, int deviceId) {
+        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName
+                + ", deviceId = " + deviceId + ")");
+        return mService.checkUidPermission(uid, permName, deviceId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index d4c6d42..35d165b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -153,9 +153,10 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int userId) {
-        int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId);
-        int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId);
+    public int getPermissionFlags(String packageName, String permName, int deviceId,
+            @UserIdInt int userId) {
+        int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
+        int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("getPermissionFlags");
@@ -165,11 +166,12 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId,
+            @UserIdInt int userId) {
         mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, userId);
+                checkAdjustPolicyFlagPermission, deviceId, userId);
         mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, userId);
+                checkAdjustPolicyFlagPermission, deviceId, userId);
     }
 
     @Override
@@ -234,16 +236,17 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int userId) {
-        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
-        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+    public void grantRuntimePermission(String packageName, String permName, int deviceId,
+            @UserIdInt int userId) {
+        mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
+        mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason) {
-        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
-        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
+    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+            @UserIdInt int userId, String reason) {
+        mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
+        mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
     }
 
     @Override
@@ -255,11 +258,11 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int userId) {
-        boolean oldVal = mOldImplementation
-                .shouldShowRequestPermissionRationale(packageName, permName, userId);
-        boolean newVal = mNewImplementation
-                .shouldShowRequestPermissionRationale(packageName, permName, userId);
+            int deviceId, @UserIdInt int userId) {
+        boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName,
+                permName, deviceId,  userId);
+        boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName,
+                permName, deviceId, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("shouldShowRequestPermissionRationale");
@@ -268,11 +271,12 @@
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
-        boolean oldVal = mOldImplementation
-                .isPermissionRevokedByPolicy(packageName, permName, userId);
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+            @UserIdInt int userId) {
+        boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName,
+                deviceId, userId);
         boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName,
-                userId);
+                deviceId, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("isPermissionRevokedByPolicy");
@@ -292,9 +296,9 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int userId) {
-        int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId);
-        int newVal = mNewImplementation.checkPermission(pkgName, permName, userId);
+    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
+        int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId);
+        int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("checkPermission");
@@ -303,9 +307,9 @@
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName) {
-        int oldVal = mOldImplementation.checkUidPermission(uid, permName);
-        int newVal = mNewImplementation.checkUidPermission(uid, permName);
+    public int checkUidPermission(int uid, String permName, int deviceId) {
+        int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId);
+        int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("checkUidPermission");
@@ -372,7 +376,7 @@
 
     @NonNull
     @Override
-    public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) {
+    public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
         Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId);
         Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId);
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index 4e72fae..cbeede0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -158,10 +158,10 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags");
         try {
-            return mService.getPermissionFlags(packageName, permName, userId);
+            return mService.getPermissionFlags(packageName, permName, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -169,12 +169,12 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags");
         try {
             mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                    checkAdjustPolicyFlagPermission, userId);
+                    checkAdjustPolicyFlagPermission, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -253,23 +253,24 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int userId) {
+    public void grantRuntimePermission(String packageName, String permName, int deviceId,
+            int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission");
         try {
-            mService.grantRuntimePermission(packageName, permName, userId);
+            mService.grantRuntimePermission(packageName, permName, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
+            int userId, String reason) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission");
         try {
-            mService.revokeRuntimePermission(packageName, permName, userId, reason);
+            mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -288,22 +289,24 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int userId) {
+            int deviceId, int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale");
         try {
-            return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
+            return mService.shouldShowRequestPermissionRationale(
+                    packageName, permName, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
+            int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy");
         try {
-            return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
+            return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -321,20 +324,20 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int userId) {
+    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission");
         try {
-            return mService.checkPermission(pkgName, permName, userId);
+            return mService.checkPermission(pkgName, permName, deviceId, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName) {
+    public int checkUidPermission(int uid, String permName, int deviceId) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission");
         try {
-            return mService.checkUidPermission(uid, permName);
+            return mService.checkUidPermission(uid, permName, deviceId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7f86f1d..faf132e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2656,7 +2656,19 @@
         }
     }
 
-    public void updateSettings() {
+    private void updateSettings() {
+        updateSettings(null);
+    }
+
+    /**
+     * Update provider Setting values on a given {@code handler}, or synchronously if {@code null}
+     * is passed for handler.
+     */
+    void updateSettings(Handler handler) {
+        if (handler != null) {
+            handler.post(() -> updateSettings(null));
+            return;
+        }
         ContentResolver resolver = mContext.getContentResolver();
         boolean updateRotation = false;
         synchronized (mLock) {
@@ -4499,6 +4511,7 @@
                 } else {
                     sleepRelease(event.getEventTime());
                 }
+                sendSystemKeyToStatusBarAsync(event);
                 break;
             }
 
@@ -4509,6 +4522,7 @@
                 if (!down) {
                     mPowerManagerInternal.setUserInactiveOverrideFromWindowManager();
                 }
+                sendSystemKeyToStatusBarAsync(event);
                 break;
             }
 
@@ -5583,12 +5597,7 @@
         mDefaultDisplayRotation.updateOrientationListener();
         synchronized (mLock) {
             mSystemReady = true;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    updateSettings();
-                }
-            });
+            updateSettings(mHandler);
             // If this happens, for whatever reason, systemReady came later than systemBooted.
             // And keyguard should be already bound from systemBooted
             if (mSystemBooted) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 19ee554..f36ecf7 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -155,6 +155,12 @@
     // ID of the current user.
     @GuardedBy("mLock")
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
+    @GuardedBy("mLock")
+    // ID of the current input displayed on the screen.
+    private String mCurrentInputId = null;
+    @GuardedBy("mLock")
+    // SessionState of the currently active TIS session.
+    private SessionState mCurrentSessionState = null;
     // IDs of the running profiles. Their parent user ID should be mCurrentUserId.
     @GuardedBy("mLock")
     private final Set<Integer> mRunningProfiles = new HashSet<>();
@@ -884,6 +890,10 @@
                 sessionState.session = null;
             }
         }
+        logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
+                mCurrentInputId, sessionState);
+        mCurrentInputId = null;
+        mCurrentSessionState = null;
         removeSessionStateLocked(sessionToken, userId);
         return sessionState;
     }
@@ -1065,6 +1075,7 @@
             Slog.e(TAG, "failed to set input info - unknown input id " + inputId);
             return;
         }
+        boolean currentCecTvInputInfoUpdated = isCurrentCecTvInputInfoUpdate(userState, inputInfo);
         inputState.info = inputInfo;
         inputState.uid = getInputUid(inputInfo);
         ServiceState serviceState = userState.serviceStateMap.get(inputInfo.getComponent());
@@ -1073,6 +1084,12 @@
             mTvInputHardwareManager.updateInputInfo(inputInfo);
         }
 
+        if (currentCecTvInputInfoUpdated) {
+            logExternalInputEvent(
+                    FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED,
+                    mCurrentInputId, mCurrentSessionState);
+        }
+
         int n = userState.mCallbacks.beginBroadcast();
         for (int i = 0; i < n; ++i) {
             try {
@@ -1085,6 +1102,29 @@
     }
 
     @GuardedBy("mLock")
+    private boolean isCurrentCecTvInputInfoUpdate(UserState userState, TvInputInfo newInputInfo) {
+        if (newInputInfo == null || newInputInfo.getId() == null
+                || !newInputInfo.getId().equals(mCurrentInputId)) {
+            return false;
+        }
+        if (newInputInfo.getHdmiDeviceInfo() == null
+                || !newInputInfo.getHdmiDeviceInfo().isCecDevice()) {
+            return false;
+        }
+        TvInputState inputState = userState.inputMap.get(mCurrentInputId);
+        if (inputState == null || inputState.info == null) {
+            return false;
+        }
+        if (inputState.info.getHdmiDeviceInfo() == null
+                || !inputState.info.getHdmiDeviceInfo().isCecDevice()) {
+            return false;
+        }
+        int newVendorId = newInputInfo.getHdmiDeviceInfo().getVendorId(),
+            currentVendorId = inputState.info.getHdmiDeviceInfo().getVendorId();
+        return newVendorId != currentVendorId;
+    }
+
+    @GuardedBy("mLock")
     private void setStateLocked(String inputId, int state, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         TvInputState inputState = userState.inputMap.get(inputId);
@@ -1101,6 +1141,24 @@
             return;
         }
         if (oldState != state) {
+            if (inputId.equals(mCurrentInputId)) {
+                logExternalInputEvent(
+                        FrameworkStatsLog
+                                .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED,
+                        mCurrentInputId, mCurrentSessionState);
+            } else if (mCurrentInputId != null) {
+                TvInputInfo currentInputInfo = userState.inputMap.get(mCurrentInputId).info;
+                if (currentInputInfo != null && currentInputInfo.getHdmiDeviceInfo() != null
+                        && inputId.equals(currentInputInfo.getParentId())) {
+                    logExternalInputEvent(
+                            FrameworkStatsLog
+                                    .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__CONNECTION_STATE_CHANGED,
+                            inputId, mCurrentSessionState);
+                    if (state == INPUT_STATE_CONNECTED_STANDBY) {
+                        mCurrentInputId = currentInputInfo.getParentId();
+                    }
+                }
+            }
             notifyInputStateChangedLocked(userState, inputId, state, null);
         }
     }
@@ -1764,10 +1822,18 @@
                         UserState userState = getOrCreateUserStateLocked(resolvedUserId);
                         SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
                                 userState);
+                        if (mCurrentInputId == null
+                                || !mCurrentInputId.equals(sessionState.inputId)) {
+                            mCurrentInputId = sessionState.inputId;
+                            logExternalInputEvent(
+                                    FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+                                    sessionState.inputId, sessionState);
+                        }
                         if (!sessionState.isCurrent
                                 || !Objects.equals(sessionState.currentChannel, channelUri)) {
                             sessionState.isCurrent = true;
                             sessionState.currentChannel = channelUri;
+                            mCurrentSessionState = sessionState;
                             notifyCurrentChannelInfosUpdatedLocked(userState);
                         }
                         if (TvContract.isChannelUriForPassthroughInput(channelUri)) {
@@ -2990,6 +3056,30 @@
                 hdmiPort);
     }
 
+    private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
+        UserState userState = getOrCreateUserStateLocked(sessionState.userId);
+        TvInputState tvInputState = userState.inputMap.get(inputId);
+        TvInputInfo tvInputInfo = tvInputState.info;
+        int inputState = tvInputState.state;
+        int inputType = tvInputInfo.getType();
+        // For non-CEC input, the value of vendorId is 0.
+        int vendorId = 0;
+        // For non-HDMI input, the value of hdmiPort is 0.
+        int hdmiPort = 0;
+        String tifSessionId = sessionState.sessionId;
+
+        if (tvInputInfo.getType() == TvInputInfo.TYPE_HDMI) {
+            HdmiDeviceInfo hdmiDeviceInfo = tvInputInfo.getHdmiDeviceInfo();
+            if (hdmiDeviceInfo != null) {
+                vendorId = hdmiDeviceInfo.getVendorId();
+                hdmiPort = hdmiDeviceInfo.getPortId();
+            }
+        }
+
+        FrameworkStatsLog.write(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT, eventType, inputState,
+                inputType, vendorId, hdmiPort, tifSessionId);
+    }
+
     private static final class UserState {
         // A mapping from the TV input id to its TvInputState.
         private Map<String, TvInputState> inputMap = new HashMap<>();
@@ -3353,6 +3443,16 @@
                 synchronized (mLock) {
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                     addHardwareInputLocked(inputInfo);
+                    // catch the use case when a CEC device is unplugged from
+                    // an HDMI port, then plugged in to the same HDMI port.
+                    if (mCurrentInputId != null && mCurrentSessionState != null
+                            && mCurrentInputId.equals(inputInfo.getParentId())
+                            && inputInfo.getId().equals(mCurrentSessionState.inputId)) {
+                        logExternalInputEvent(
+                                FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+                                inputInfo.getId(), mCurrentSessionState);
+                        mCurrentInputId = inputInfo.getId();
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -3455,6 +3555,8 @@
                         UserState userState = getOrCreateUserStateLocked(mSessionState.userId);
                         mSessionState.isCurrent = true;
                         mSessionState.currentChannel = channelUri;
+                        mCurrentSessionState = mSessionState;
+                        mCurrentInputId = mSessionState.inputId;
                         notifyCurrentChannelInfosUpdatedLocked(userState);
                     }
                 } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
index 48d477c..111e075a 100644
--- a/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
+++ b/services/core/java/com/android/server/vibrator/ClippingAmplitudeAndFrequencyAdapter.java
@@ -30,7 +30,7 @@
  * each frequency.
  *
  * <p>The {@link VibratorInfo.FrequencyProfile} is only applicable to PWLE compositions. This
- * adapter is only applied to {@link RampSegment} and leaves all other segments unchanged.
+ * adapter is only applied to {@link RampSegment} and all other segments will remain unchanged.
  */
 final class ClippingAmplitudeAndFrequencyAdapter implements VibrationSegmentsAdapter {
 
@@ -41,13 +41,13 @@
         for (int i = 0; i < segmentCount; i++) {
             VibrationEffectSegment segment = segments.get(i);
             if (segment instanceof RampSegment) {
-                segments.set(i, apply((RampSegment) segment, info));
+                segments.set(i, adaptToVibrator(info, (RampSegment) segment));
             }
         }
         return repeatIndex;
     }
 
-    private RampSegment apply(RampSegment segment, VibratorInfo info) {
+    private RampSegment adaptToVibrator(VibratorInfo info, RampSegment segment) {
         float clampedStartFrequency = clampFrequency(info, segment.getStartFrequencyHz());
         float clampedEndFrequency = clampFrequency(info, segment.getEndFrequencyHz());
         return new RampSegment(
diff --git a/services/core/java/com/android/server/vibrator/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index 41649fa..98309cd 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -53,9 +53,14 @@
         mSegmentAdapters = Arrays.asList(
                 // TODO(b/167947076): add filter that removes unsupported primitives
                 // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+                // Convert segments based on device capabilities
                 new RampToStepAdapter(settings.getRampStepDuration()),
                 new StepToRampAdapter(),
+                // Add extra ramp down segments as needed
                 new RampDownAdapter(settings.getRampDownDuration(), settings.getRampStepDuration()),
+                // Split segments based on their duration and device supported limits
+                new SplitSegmentsAdapter(),
+                // Clip amplitudes and frequencies of final segments based on device bandwidth curve
                 new ClippingAmplitudeAndFrequencyAdapter()
         );
         mAvailableVibrators = vibrators;
diff --git a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
index 9e248cd..fc19e27 100644
--- a/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
+++ b/services/core/java/com/android/server/vibrator/RampToStepAdapter.java
@@ -28,9 +28,11 @@
 import java.util.List;
 
 /**
- * Adapter that converts ramp segments that to a sequence of fixed step segments.
+ * Adapter that converts ramp segments to a sequence of fixed step segments.
  *
- * <p>This leaves the list unchanged if the device has compose PWLE capability.
+ * <p>This change preserves the frequency parameters by interpolating the ramp values.
+ *
+ * <p>The segments will not be changed if the device has {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
  */
 final class RampToStepAdapter implements VibrationSegmentsAdapter {
 
@@ -53,7 +55,7 @@
             if (!(segment instanceof RampSegment)) {
                 continue;
             }
-            List<StepSegment> steps = apply(info, (RampSegment) segment);
+            List<StepSegment> steps = convertRampToSteps(info, (RampSegment) segment);
             segments.remove(i);
             segments.addAll(i, steps);
             int addedSegments = steps.size() - 1;
@@ -66,7 +68,7 @@
         return repeatIndex;
     }
 
-    private List<StepSegment> apply(VibratorInfo info, RampSegment ramp) {
+    private List<StepSegment> convertRampToSteps(VibratorInfo info, RampSegment ramp) {
         if (Float.compare(ramp.getStartAmplitude(), ramp.getEndAmplitude()) == 0) {
             // Amplitude is the same, so return a single step to simulate this ramp.
             return Arrays.asList(
diff --git a/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.java
new file mode 100644
index 0000000..347db35
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/SplitSegmentsAdapter.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 com.android.server.vibrator;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.util.MathUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Adapter that splits segments with longer duration than the device capabilities.
+ *
+ * <p>This transformation replaces large {@link RampSegment} entries by a sequence of smaller
+ * ramp segments that starts and ends at the same amplitudes/frequencies, interpolating the
+ * intermediate values.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
+ */
+final class SplitSegmentsAdapter implements VibrationSegmentsAdapter {
+
+    @Override
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
+        if (!info.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)) {
+            // The vibrator does not have PWLE capability, so keep the segments unchanged.
+            return repeatIndex;
+        }
+        int maxRampDuration = info.getPwlePrimitiveDurationMax();
+        if (maxRampDuration <= 0) {
+            // No limit set to PWLE primitive duration.
+            return repeatIndex;
+        }
+
+        int segmentCount = segments.size();
+        for (int i = 0; i < segmentCount; i++) {
+            if (!(segments.get(i) instanceof RampSegment)) {
+                continue;
+            }
+            RampSegment ramp = (RampSegment) segments.get(i);
+            int splits = ((int) ramp.getDuration() + maxRampDuration - 1) / maxRampDuration;
+            if (splits <= 1) {
+                continue;
+            }
+            segments.remove(i);
+            segments.addAll(i, splitRampSegment(info, ramp, splits));
+            int addedSegments = splits - 1;
+            if (repeatIndex > i) {
+                repeatIndex += addedSegments;
+            }
+            i += addedSegments;
+            segmentCount += addedSegments;
+        }
+
+        return repeatIndex;
+    }
+
+    private static List<RampSegment> splitRampSegment(VibratorInfo info, RampSegment ramp,
+            int splits) {
+        List<RampSegment> ramps = new ArrayList<>(splits);
+        // Fill zero frequency values with the device resonant frequency before interpolating.
+        float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz());
+        float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz());
+        long splitDuration = ramp.getDuration() / splits;
+        float previousAmplitude = ramp.getStartAmplitude();
+        float previousFrequencyHz = startFrequencyHz;
+        long accumulatedDuration = 0;
+
+        for (int i = 1; i < splits; i++) {
+            accumulatedDuration += splitDuration;
+            float durationRatio = (float) accumulatedDuration / ramp.getDuration();
+            float interpolatedFrequency =
+                    MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
+            float interpolatedAmplitude =
+                    MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio);
+            RampSegment rampSplit = new RampSegment(
+                    previousAmplitude, interpolatedAmplitude,
+                    previousFrequencyHz, interpolatedFrequency,
+                    (int) splitDuration);
+            ramps.add(rampSplit);
+            previousAmplitude = rampSplit.getEndAmplitude();
+            previousFrequencyHz = rampSplit.getEndFrequencyHz();
+        }
+
+        ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequencyHz,
+                endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration)));
+
+        return ramps;
+    }
+
+    private static float fillEmptyFrequency(VibratorInfo info, float frequencyHz) {
+        if (Float.isNaN(info.getResonantFrequencyHz())) {
+            return frequencyHz;
+        }
+        return frequencyHz == 0 ? info.getResonantFrequencyHz() : frequencyHz;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
index d86ee78..6616f10 100644
--- a/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
+++ b/services/core/java/com/android/server/vibrator/StepToRampAdapter.java
@@ -21,17 +21,17 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
-import android.util.MathUtils;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Adapter that converts step segments that should be handled as PWLEs to ramp segments.
  *
- * <p>Each replaced {@link StepSegment} will be represented by a {@link RampSegment} with same
- * start and end amplitudes/frequencies, which can then be converted to PWLE compositions. This
- * adapter leaves the segments unchanged if the device doesn't have the PWLE composition capability.
+ * <p>Each replaced step will be represented by a ramp with same start and end
+ * amplitudes/frequencies, which can then be converted to PWLE compositions.
+ *
+ * <p>The segments will not be changed if the device doesn't have
+ * {@link IVibrator#CAP_COMPOSE_PWLE_EFFECTS}.
  */
 final class StepToRampAdapter implements VibrationSegmentsAdapter {
 
@@ -42,12 +42,6 @@
             // The vibrator does not have PWLE capability, so keep the segments unchanged.
             return repeatIndex;
         }
-        convertStepsToRamps(info, segments);
-        repeatIndex = splitLongRampSegments(info, segments, repeatIndex);
-        return repeatIndex;
-    }
-
-    private void convertStepsToRamps(VibratorInfo info, List<VibrationEffectSegment> segments) {
         int segmentCount = segments.size();
         // Convert steps that require frequency control to ramps.
         for (int i = 0; i < segmentCount; i++) {
@@ -68,40 +62,6 @@
                 }
             }
         }
-    }
-
-    /**
-     * Split {@link RampSegment} entries that have duration longer than {@link
-     * VibratorInfo#getPwlePrimitiveDurationMax()}.
-     */
-    private int splitLongRampSegments(VibratorInfo info, List<VibrationEffectSegment> segments,
-            int repeatIndex) {
-        int maxDuration = info.getPwlePrimitiveDurationMax();
-        if (maxDuration <= 0) {
-            // No limit set to PWLE primitive duration.
-            return repeatIndex;
-        }
-
-        int segmentCount = segments.size();
-        for (int i = 0; i < segmentCount; i++) {
-            if (!(segments.get(i) instanceof RampSegment)) {
-                continue;
-            }
-            RampSegment ramp = (RampSegment) segments.get(i);
-            int splits = ((int) ramp.getDuration() + maxDuration - 1) / maxDuration;
-            if (splits <= 1) {
-                continue;
-            }
-            segments.remove(i);
-            segments.addAll(i, splitRampSegment(info, ramp, splits));
-            int addedSegments = splits - 1;
-            if (repeatIndex > i) {
-                repeatIndex += addedSegments;
-            }
-            i += addedSegments;
-            segmentCount += addedSegments;
-        }
-
         return repeatIndex;
     }
 
@@ -111,38 +71,6 @@
                 frequencyHz, frequencyHz, (int) segment.getDuration());
     }
 
-    private static List<RampSegment> splitRampSegment(VibratorInfo info, RampSegment ramp,
-            int splits) {
-        List<RampSegment> ramps = new ArrayList<>(splits);
-        float startFrequencyHz = fillEmptyFrequency(info, ramp.getStartFrequencyHz());
-        float endFrequencyHz = fillEmptyFrequency(info, ramp.getEndFrequencyHz());
-        long splitDuration = ramp.getDuration() / splits;
-        float previousAmplitude = ramp.getStartAmplitude();
-        float previousFrequency = startFrequencyHz;
-        long accumulatedDuration = 0;
-
-        for (int i = 1; i < splits; i++) {
-            accumulatedDuration += splitDuration;
-            float durationRatio = (float) accumulatedDuration / ramp.getDuration();
-            float interpolatedFrequency =
-                    MathUtils.lerp(startFrequencyHz, endFrequencyHz, durationRatio);
-            float interpolatedAmplitude =
-                    MathUtils.lerp(ramp.getStartAmplitude(), ramp.getEndAmplitude(), durationRatio);
-            RampSegment rampSplit = new RampSegment(
-                    previousAmplitude, interpolatedAmplitude,
-                    previousFrequency, interpolatedFrequency,
-                    (int) splitDuration);
-            ramps.add(rampSplit);
-            previousAmplitude = rampSplit.getEndAmplitude();
-            previousFrequency = rampSplit.getEndFrequencyHz();
-        }
-
-        ramps.add(new RampSegment(previousAmplitude, ramp.getEndAmplitude(), previousFrequency,
-                endFrequencyHz, (int) (ramp.getDuration() - accumulatedDuration)));
-
-        return ramps;
-    }
-
     private static boolean isStep(VibrationEffectSegment segment) {
         return segment instanceof StepSegment;
     }
diff --git a/services/core/java/com/android/server/vibrator/TEST_MAPPING b/services/core/java/com/android/server/vibrator/TEST_MAPPING
index f0a7e47..92b327d 100644
--- a/services/core/java/com/android/server/vibrator/TEST_MAPPING
+++ b/services/core/java/com/android/server/vibrator/TEST_MAPPING
@@ -1,21 +1,10 @@
 {
-  "presubmit": [
+  "imports": [
     {
-      "name": "FrameworksVibratorServicesTests",
-      "options": [
-        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
-    }
-  ],
-  "postsubmit": [
+      "path": "frameworks/base/services/tests/vibrator"
+    },
     {
-      "name": "FrameworksVibratorServicesTests",
-      "options": [
-        {"exclude-annotation": "org.junit.Ignore"}
-      ]
+      "path": "cts/tests/vibrator"
     }
   ]
 }
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 4f7f13e..fed6e7e 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.media.AudioAttributes;
 import android.os.CombinedVibration;
 import android.os.IBinder;
 import android.os.VibrationAttributes;
@@ -27,8 +28,10 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.Locale;
@@ -39,7 +42,9 @@
  * The base class for all vibrations.
  */
 abstract class Vibration {
-    private static final SimpleDateFormat DEBUG_DATE_FORMAT =
+    private static final SimpleDateFormat DEBUG_TIME_FORMAT =
+            new SimpleDateFormat("HH:mm:ss.SSS");
+    private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
             new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
     // Used to generate globally unique vibration ids.
     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -146,10 +151,10 @@
         @Override
         public String toString() {
             return "CallerInfo{"
-                    + " attrs=" + attrs
-                    + ", uid=" + uid
-                    + ", displayId=" + displayId
+                    + " uid=" + uid
                     + ", opPkg=" + opPkg
+                    + ", displayId=" + displayId
+                    + ", attrs=" + attrs
                     + ", reason=" + reason
                     + '}';
         }
@@ -203,14 +208,17 @@
      * potentially expensive or resource-linked objects, such as {@link IBinder}.
      */
     static final class DebugInfo {
-        private final long mCreateTime;
+        final long mCreateTime;
+        final CallerInfo mCallerInfo;
+        @Nullable
+        final CombinedVibration mPlayedEffect;
+
         private final long mStartTime;
         private final long mEndTime;
         private final long mDurationMs;
-        @Nullable private final CombinedVibration mOriginalEffect;
-        @Nullable private final CombinedVibration mPlayedEffect;
+        @Nullable
+        private final CombinedVibration mOriginalEffect;
         private final float mScale;
-        private final CallerInfo mCallerInfo;
         private final Status mStatus;
 
         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
@@ -230,10 +238,10 @@
 
         @Override
         public String toString() {
-            return "createTime: " + DEBUG_DATE_FORMAT.format(new Date(mCreateTime))
-                    + ", startTime: " + DEBUG_DATE_FORMAT.format(new Date(mStartTime))
+            return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
+                    + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
                     + ", endTime: "
-                    + (mEndTime == 0 ? null : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
+                    + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
                     + ", durationMs: " + mDurationMs
                     + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
                     + ", playedEffect: " + mPlayedEffect
@@ -242,8 +250,56 @@
                     + ", callerInfo: " + mCallerInfo;
         }
 
+        /**
+         * Write this info in a compact way into given {@link PrintWriter}.
+         *
+         * <p>This is used by dumpsys to log multiple vibration records in single lines that are
+         * easy to skim through by the sorted created time.
+         */
+        void dumpCompact(IndentingPrintWriter pw) {
+            boolean isExternalVibration = mPlayedEffect == null;
+            String timingsStr = String.format(Locale.ROOT,
+                    "%s | %8s | %20s | duration: %5dms | start: %12s | end: %10s",
+                    DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+                    isExternalVibration ? "external" : "effect",
+                    mStatus.name().toLowerCase(Locale.ROOT),
+                    mDurationMs,
+                    mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
+                    mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+            String callerInfoStr = String.format(Locale.ROOT,
+                    " | %s (uid=%d, displayId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
+                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.displayId,
+                    mCallerInfo.attrs.usageToString(),
+                    AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()),
+                    Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+                    mCallerInfo.reason);
+            String effectStr = String.format(Locale.ROOT,
+                    " | played: %s | original: %s | scale: %.2f",
+                    mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
+                    mOriginalEffect == null ? null : mOriginalEffect.toDebugString(),
+                    mScale);
+            pw.println(timingsStr + callerInfoStr + effectStr);
+        }
+
+        /** Write this info into given {@link PrintWriter}. */
+        void dump(IndentingPrintWriter pw) {
+            pw.println("Vibration:");
+            pw.increaseIndent();
+            pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+            pw.println("durationMs = " + mDurationMs);
+            pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
+            pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
+            pw.println("endTime = "
+                    + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+            pw.println("playedEffect = " + mPlayedEffect);
+            pw.println("originalEffect = " + mOriginalEffect);
+            pw.println("scale = " + String.format(Locale.ROOT, "%.2f", mScale));
+            pw.println("callerInfo = " + mCallerInfo);
+            pw.decreaseIndent();
+        }
+
         /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
-        public void dumpProto(ProtoOutputStream proto, long fieldId) {
+        void dump(ProtoOutputStream proto, long fieldId) {
             final long token = proto.start(fieldId);
             proto.write(VibrationProto.START_TIME, mStartTime);
             proto.write(VibrationProto.END_TIME, mEndTime);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index dbd6bf4..db8a9ae 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -54,6 +54,7 @@
 import android.os.Vibrator.VibrationIntensity;
 import android.os.vibrator.VibrationConfig;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -65,6 +66,7 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -601,8 +603,32 @@
         }
     }
 
+    /** Write current settings into given {@link PrintWriter}. */
+    void dump(IndentingPrintWriter pw) {
+        pw.println("VibrationSettings:");
+        pw.increaseIndent();
+        pw.println("vibrateOn = " + mVibrateOn);
+        pw.println("vibrateInputDevices = " + mVibrateInputDevices);
+        pw.println("batterySaverMode = " + mBatterySaverMode);
+        pw.println("VibrationIntensities:");
+
+        pw.increaseIndent();
+        for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
+            int usage = mCurrentVibrationIntensities.keyAt(i);
+            int intensity = mCurrentVibrationIntensities.valueAt(i);
+            pw.println(VibrationAttributes.usageToString(usage) + " = "
+                    + intensityToString(intensity)
+                    + ", default: " + intensityToString(getDefaultIntensity(usage)));
+        }
+        pw.decreaseIndent();
+
+        mVibrationConfig.dumpWithoutDefaultSettings(pw);
+        pw.println("processStateCache = " + mUidObserver.mProcStatesCache);
+        pw.decreaseIndent();
+    }
+
     /** Write current settings into given {@link ProtoOutputStream}. */
-    public void dumpProto(ProtoOutputStream proto) {
+    void dump(ProtoOutputStream proto) {
         synchronized (mLock) {
             proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
             proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 47b3e1a..f5d4d1e 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -17,6 +17,7 @@
 package com.android.server.vibrator;
 
 import android.annotation.Nullable;
+import android.annotation.Nullable;
 import android.hardware.vibrator.IVibrator;
 import android.os.Binder;
 import android.os.IVibratorStateListener;
@@ -26,6 +27,7 @@
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.RampSegment;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -351,6 +353,19 @@
                 + '}';
     }
 
+    void dump(IndentingPrintWriter pw) {
+        pw.println("VibratorController:");
+        pw.increaseIndent();
+        pw.println("isVibrating = " + mIsVibrating);
+        pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
+        pw.println("currentAmplitude = " + mCurrentAmplitude);
+        pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
+        pw.println("vibratorStateListenerCount = "
+                + mVibratorStateListeners.getRegisteredCallbackCount());
+        mVibratorInfo.dump(pw);
+        pw.decreaseIndent();
+    }
+
     @GuardedBy("mLock")
     private void notifyListenerOnVibrating(boolean isVibrating) {
         if (mIsVibrating != isVibrating) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 6fdb1db..270f7f0c 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -56,6 +56,7 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.os.vibrator.persistence.VibrationXmlParser;
 import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,7 @@
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -204,9 +206,15 @@
         mNativeWrapper = injector.getNativeWrapper();
         mNativeWrapper.init(listener);
 
-        int dumpLimit = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_previousVibrationsDumpLimit);
-        mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+        int recentDumpSizeLimit = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_recentVibrationsDumpSizeLimit);
+        int dumpSizeLimit = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit);
+        int dumpAggregationTimeLimit = mContext.getResources().getInteger(
+                com.android.internal.R.integer
+                        .config_previousVibrationsDumpAggregationTimeMillisLimit);
+        mVibratorManagerRecords = new VibratorManagerRecords(
+                recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
 
         mBatteryStatsService = injector.getBatteryStatsService();
         mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
@@ -544,49 +552,74 @@
         }
     }
 
-    private void dumpText(PrintWriter pw) {
+    private void dumpText(PrintWriter w) {
         if (DEBUG) {
             Slog.d(TAG, "Dumping vibrator manager service to text...");
         }
+        IndentingPrintWriter pw = new IndentingPrintWriter(w, /* singleIndent= */ "  ");
         synchronized (mLock) {
             pw.println("Vibrator Manager Service:");
-            pw.println("  mVibrationSettings:");
-            pw.println("    " + mVibrationSettings);
+            pw.increaseIndent();
+
+            mVibrationSettings.dump(pw);
             pw.println();
-            pw.println("  mVibratorControllers:");
+
+            pw.println("VibratorControllers:");
+            pw.increaseIndent();
             for (int i = 0; i < mVibrators.size(); i++) {
-                pw.println("    " + mVibrators.valueAt(i));
+                mVibrators.valueAt(i).dump(pw);
             }
+            pw.decreaseIndent();
             pw.println();
-            pw.println("  mCurrentVibration:");
-            pw.println("    " + (mCurrentVibration == null
-                    ? null : mCurrentVibration.getVibration().getDebugInfo()));
+
+            pw.println("CurrentVibration:");
+            pw.increaseIndent();
+            if (mCurrentVibration != null) {
+                mCurrentVibration.getVibration().getDebugInfo().dump(pw);
+            } else {
+                pw.println("null");
+            }
+            pw.decreaseIndent();
             pw.println();
-            pw.println("  mNextVibration:");
-            pw.println("    " + (mNextVibration == null
-                    ? null : mNextVibration.getVibration().getDebugInfo()));
+
+            pw.println("NextVibration:");
+            pw.increaseIndent();
+            if (mNextVibration != null) {
+                mNextVibration.getVibration().getDebugInfo().dump(pw);
+            } else {
+                pw.println("null");
+            }
+            pw.decreaseIndent();
             pw.println();
-            pw.println("  mCurrentExternalVibration:");
-            pw.println("    " + (mCurrentExternalVibration == null
-                    ? null : mCurrentExternalVibration.getDebugInfo()));
-            pw.println();
+
+            pw.println("CurrentExternalVibration:");
+            pw.increaseIndent();
+            if (mCurrentExternalVibration != null) {
+                mCurrentExternalVibration.getDebugInfo().dump(pw);
+            } else {
+                pw.println("null");
+            }
+            pw.decreaseIndent();
         }
-        mVibratorManagerRecords.dumpText(pw);
+
+        pw.println();
+        pw.println();
+        mVibratorManagerRecords.dump(pw);
     }
 
-    synchronized void dumpProto(FileDescriptor fd) {
+    private void dumpProto(FileDescriptor fd) {
         final ProtoOutputStream proto = new ProtoOutputStream(fd);
         if (DEBUG) {
             Slog.d(TAG, "Dumping vibrator manager service to proto...");
         }
         synchronized (mLock) {
-            mVibrationSettings.dumpProto(proto);
+            mVibrationSettings.dump(proto);
             if (mCurrentVibration != null) {
-                mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+                mCurrentVibration.getVibration().getDebugInfo().dump(proto,
                         VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
             }
             if (mCurrentExternalVibration != null) {
-                mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+                mCurrentExternalVibration.getDebugInfo().dump(proto,
                         VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
             }
 
@@ -601,7 +634,7 @@
             proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
                     isUnderExternalControl);
         }
-        mVibratorManagerRecords.dumpProto(proto);
+        mVibratorManagerRecords.dump(proto);
         proto.flush();
     }
 
@@ -1581,64 +1614,105 @@
 
     /** Keep records of vibrations played and provide debug information for this service. */
     private static final class VibratorManagerRecords {
-        private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
-                new SparseArray<>();
-        private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
-                new LinkedList<>();
-        private final int mPreviousVibrationsLimit;
+        private final VibrationRecords mAggregatedVibrationHistory;
+        private final VibrationRecords mRecentVibrations;
 
-        VibratorManagerRecords(int limit) {
-            mPreviousVibrationsLimit = limit;
+        VibratorManagerRecords(int recentVibrationSizeLimit, int aggregationSizeLimit,
+                int aggregationTimeLimit) {
+            mAggregatedVibrationHistory =
+                    new VibrationRecords(aggregationSizeLimit, aggregationTimeLimit);
+            mRecentVibrations = new VibrationRecords(
+                    recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
         }
 
         synchronized void record(HalVibration vib) {
-            int usage = vib.callerInfo.attrs.getUsage();
-            if (!mPreviousVibrations.contains(usage)) {
-                mPreviousVibrations.put(usage, new LinkedList<>());
-            }
-            record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+            record(vib.getDebugInfo());
         }
 
         synchronized void record(ExternalVibrationHolder vib) {
-            record(mPreviousExternalVibrations, vib.getDebugInfo());
+            record(vib.getDebugInfo());
         }
 
-        synchronized void record(LinkedList<Vibration.DebugInfo> records,
-                Vibration.DebugInfo info) {
-            if (records.size() > mPreviousVibrationsLimit) {
-                records.removeFirst();
+        private synchronized void record(Vibration.DebugInfo info) {
+            AggregatedVibrationRecord removedRecord = mRecentVibrations.record(info);
+            if (removedRecord != null) {
+                mAggregatedVibrationHistory.record(removedRecord.mLatestVibration);
             }
-            records.addLast(info);
         }
 
-        synchronized void dumpText(PrintWriter pw) {
-            for (int i = 0; i < mPreviousVibrations.size(); i++) {
-                pw.println();
-                pw.print("  Previous vibrations for usage ");
-                pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
-                pw.println(":");
-                for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
-                    pw.println("    " + info);
+        synchronized void dump(IndentingPrintWriter pw) {
+            pw.println("Recent vibrations:");
+            pw.increaseIndent();
+            mRecentVibrations.dump(pw);
+            pw.decreaseIndent();
+            pw.println();
+            pw.println();
+
+            pw.println("Aggregated vibration history:");
+            pw.increaseIndent();
+            mAggregatedVibrationHistory.dump(pw);
+            pw.decreaseIndent();
+        }
+
+        synchronized void dump(ProtoOutputStream proto) {
+            mRecentVibrations.dump(proto);
+        }
+    }
+
+    /** Keep records of vibrations played and provide debug information for this service. */
+    private static final class VibrationRecords {
+        private final SparseArray<LinkedList<AggregatedVibrationRecord>> mVibrations =
+                new SparseArray<>();
+        private final int mSizeLimit;
+        private final int mAggregationTimeLimit;
+
+        VibrationRecords(int sizeLimit, int aggregationTimeLimit) {
+            mSizeLimit = sizeLimit;
+            mAggregationTimeLimit = aggregationTimeLimit;
+        }
+
+        synchronized AggregatedVibrationRecord record(Vibration.DebugInfo info) {
+            int usage = info.mCallerInfo.attrs.getUsage();
+            if (!mVibrations.contains(usage)) {
+                mVibrations.put(usage, new LinkedList<>());
+            }
+            LinkedList<AggregatedVibrationRecord> records = mVibrations.get(usage);
+            if (mAggregationTimeLimit > 0 && !records.isEmpty()) {
+                AggregatedVibrationRecord lastRecord = records.getLast();
+                if (lastRecord.mayAggregate(info, mAggregationTimeLimit)) {
+                    lastRecord.record(info);
+                    return null;
                 }
             }
+            AggregatedVibrationRecord removedRecord = null;
+            if (records.size() > mSizeLimit) {
+                removedRecord = records.removeFirst();
+            }
+            records.addLast(new AggregatedVibrationRecord(info));
+            return removedRecord;
+        }
 
-            pw.println();
-            pw.println("  Previous external vibrations:");
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                pw.println("    " + info);
+        synchronized void dump(IndentingPrintWriter pw) {
+            for (int i = 0; i < mVibrations.size(); i++) {
+                pw.println(VibrationAttributes.usageToString(mVibrations.keyAt(i)) + ":");
+                pw.increaseIndent();
+                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+                    info.dump(pw);
+                }
+                pw.decreaseIndent();
+                pw.println();
             }
         }
 
-        synchronized void dumpProto(ProtoOutputStream proto) {
-            for (int i = 0; i < mPreviousVibrations.size(); i++) {
+        synchronized void dump(ProtoOutputStream proto) {
+            for (int i = 0; i < mVibrations.size(); i++) {
                 long fieldId;
-                switch (mPreviousVibrations.keyAt(i)) {
+                switch (mVibrations.keyAt(i)) {
                     case VibrationAttributes.USAGE_RINGTONE:
                         fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
                         break;
                     case VibrationAttributes.USAGE_NOTIFICATION:
-                        fieldId = VibratorManagerServiceDumpProto
-                                .PREVIOUS_NOTIFICATION_VIBRATIONS;
+                        fieldId = VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS;
                         break;
                     case VibrationAttributes.USAGE_ALARM:
                         fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
@@ -1646,18 +1720,70 @@
                     default:
                         fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
                 }
-                for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
-                    info.dumpProto(proto, fieldId);
+                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+                    if (info.mLatestVibration.mPlayedEffect == null) {
+                        // External vibrations are reported separately in the dump proto
+                        info.dump(proto,
+                                VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+                    } else {
+                        info.dump(proto, fieldId);
+                    }
                 }
             }
+        }
 
-            for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
-                info.dumpProto(proto,
-                        VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+        synchronized void dumpOnSingleField(ProtoOutputStream proto, long fieldId) {
+            for (int i = 0; i < mVibrations.size(); i++) {
+                for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+                    info.dump(proto, fieldId);
+                }
             }
         }
     }
 
+    /**
+     * Record that keeps the last {@link Vibration.DebugInfo} played, aggregating close vibrations
+     * from the same uid that have the same {@link VibrationAttributes} and {@link VibrationEffect}.
+     */
+    private static final class AggregatedVibrationRecord {
+        private final Vibration.DebugInfo mFirstVibration;
+        private Vibration.DebugInfo mLatestVibration;
+        private int mVibrationCount;
+
+        AggregatedVibrationRecord(Vibration.DebugInfo info) {
+            mLatestVibration = mFirstVibration = info;
+            mVibrationCount = 1;
+        }
+
+        synchronized boolean mayAggregate(Vibration.DebugInfo info, long timeLimit) {
+            return Objects.equals(mLatestVibration.mCallerInfo.uid, info.mCallerInfo.uid)
+                    && Objects.equals(mLatestVibration.mCallerInfo.attrs, info.mCallerInfo.attrs)
+                    && Objects.equals(mLatestVibration.mPlayedEffect, info.mPlayedEffect)
+                    && Math.abs(mLatestVibration.mCreateTime - info.mCreateTime) < timeLimit;
+        }
+
+        synchronized void record(Vibration.DebugInfo vib) {
+            mLatestVibration = vib;
+            mVibrationCount++;
+        }
+
+        synchronized void dump(IndentingPrintWriter pw) {
+            mFirstVibration.dumpCompact(pw);
+            if (mVibrationCount == 1) {
+                return;
+            }
+            if (mVibrationCount > 2) {
+                pw.println(
+                        "-> Skipping " + (mVibrationCount - 2) + " aggregated vibrations, latest:");
+            }
+            mLatestVibration.dumpCompact(pw);
+        }
+
+        synchronized void dump(ProtoOutputStream proto, long fieldId) {
+            mLatestVibration.dump(proto, fieldId);
+        }
+    }
+
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
     private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
@@ -1676,7 +1802,7 @@
     /**
      * Ends the external vibration, and clears related service state.
      *
-     * @param vibrationEndInfo the status and related info to end the associated Vibration with
+     * @param vibrationEndInfo        the status and related info to end the associated Vibration
      * @param continueExternalControl indicates whether external control will continue. If not, the
      *                                HAL will have external control turned off.
      */
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ea06b42..a7849c1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -226,6 +226,7 @@
 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.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;
@@ -2682,7 +2683,9 @@
     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()) {
@@ -7644,7 +7647,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) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 0c196d7..976641b 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,7 @@
                 if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
                     return null;
                 }
-                applyPreviewStrategy(mOpenTarget, openActivity);
+                applyPreviewStrategy(mOpenAdaptor, openActivity);
 
                 final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
                 final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1220,6 +1278,7 @@
         if (mPendingAnimation != null) {
             mPendingAnimation.run();
             mPendingAnimation = null;
+            mBackAnimationRunning = true;
         }
     }
 
@@ -1236,9 +1295,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 +1303,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 +1326,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/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index cff86ad..2b22d75 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,10 @@
      */
     Task mAssociatedTask;
 
+
+    /** Whether the starting window is resized from transfer across activities. */
+    boolean mResizedFromTransfer;
+
     /** Whether the starting window is drawn. */
     boolean mIsDisplayed;
 
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/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 789e3d2..7a904f8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1499,7 +1499,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);
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 26aab07..726ae5c 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -76,7 +76,7 @@
             @NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
             @WindowType int type, @Nullable Bundle options) {
         registerWindowContainerListener(wpc, clientToken, container, type, options,
-                true /* shouDispatchConfigWhenRegistering */);
+                true /* shouldDispatchConfigWhenRegistering */);
     }
 
     /**
@@ -91,19 +91,19 @@
      * @param container the {@link WindowContainer} which the listener is going to listen to.
      * @param type the window type
      * @param options a bundle used to pass window-related options.
-     * @param shouDispatchConfigWhenRegistering {@code true} to indicate the current
+     * @param shouldDispatchConfigWhenRegistering {@code true} to indicate the current
      *                {@code container}'s config will dispatch to the client side when
      *                registering the {@link WindowContextListenerImpl}
      */
     void registerWindowContainerListener(@NonNull WindowProcessController wpc,
             @NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
             @WindowType int type, @Nullable Bundle options,
-            boolean shouDispatchConfigWhenRegistering) {
+            boolean shouldDispatchConfigWhenRegistering) {
         WindowContextListenerImpl listener = mListeners.get(clientToken);
         if (listener == null) {
             listener = new WindowContextListenerImpl(wpc, clientToken, container, type,
                     options);
-            listener.register(shouDispatchConfigWhenRegistering);
+            listener.register(shouldDispatchConfigWhenRegistering);
         } else {
             updateContainerForWindowContextListener(clientToken, container);
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 210378f..261d6bc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -305,6 +305,7 @@
 import android.window.ScreenCapture;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
+import android.window.WindowContextInfo;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -2737,7 +2738,7 @@
 
     @Nullable
     @Override
-    public Configuration attachWindowContextToDisplayArea(@NonNull IApplicationThread appThread,
+    public WindowContextInfo attachWindowContextToDisplayArea(@NonNull IApplicationThread appThread,
             @NonNull IBinder clientToken, @LayoutParams.WindowType int type, int displayId,
             @Nullable Bundle options) {
         Objects.requireNonNull(appThread);
@@ -2766,8 +2767,8 @@
                 final DisplayArea<?> da = dc.findAreaForWindowType(type, options,
                         callerCanManageAppTokens, false /* roundedCornerOverlay */);
                 mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
-                        da, type, options, false /* shouDispatchConfigWhenRegistering */);
-                return da.getConfiguration();
+                        da, type, options, false /* shouldDispatchConfigWhenRegistering */);
+                return new WindowContextInfo(da.getConfiguration(), displayId);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -2776,8 +2777,8 @@
 
     @Nullable
     @Override
-    public Configuration attachWindowContextToDisplayContent(@NonNull IApplicationThread appThread,
-            @NonNull IBinder clientToken, int displayId) {
+    public WindowContextInfo attachWindowContextToDisplayContent(
+            @NonNull IApplicationThread appThread, @NonNull IBinder clientToken, int displayId) {
         Objects.requireNonNull(appThread);
         Objects.requireNonNull(clientToken);
         final int callingPid = Binder.getCallingPid();
@@ -2809,16 +2810,17 @@
 
                 mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
                         dc, INVALID_WINDOW_TYPE, null /* options */,
-                        false /* shouDispatchConfigWhenRegistering */);
-                return dc.getConfiguration();
+                        false /* shouldDispatchConfigWhenRegistering */);
+                return new WindowContextInfo(dc.getConfiguration(), displayId);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    @Nullable
     @Override
-    public void attachWindowContextToWindowToken(@NonNull IApplicationThread appThread,
+    public WindowContextInfo attachWindowContextToWindowToken(@NonNull IApplicationThread appThread,
             @NonNull IBinder clientToken, @NonNull IBinder token) {
         Objects.requireNonNull(appThread);
         Objects.requireNonNull(clientToken);
@@ -2834,13 +2836,13 @@
                 if (wpc == null) {
                     ProtoLog.w(WM_ERROR, "attachWindowContextToWindowToken: calling from"
                             + " non-existing process pid=%d uid=%d", callingPid, callingUid);
-                    return;
+                    return null;
                 }
                 final WindowToken windowToken = mRoot.getWindowToken(token);
                 if (windowToken == null) {
                     ProtoLog.w(WM_ERROR, "Then token:%s is invalid. It might be "
                             + "removed", token);
-                    return;
+                    return null;
                 }
                 final int type = mWindowContextListenerController.getWindowType(clientToken);
                 if (type == INVALID_WINDOW_TYPE) {
@@ -2854,10 +2856,13 @@
                 }
                 if (!mWindowContextListenerController.assertCallerCanModifyListener(clientToken,
                         callerCanManageAppTokens, callingUid)) {
-                    return;
+                    return null;
                 }
                 mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
-                        windowToken, windowToken.windowType, windowToken.mOptions);
+                        windowToken, windowToken.windowType, windowToken.mOptions,
+                                               false /* shouldDispatchConfigWhenRegistering */);
+                return new WindowContextInfo(windowToken.getConfiguration(),
+                        windowToken.getDisplayContent().getDisplayId());
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index baf97b7..029f46f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1935,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();
@@ -2330,6 +2332,13 @@
             // 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.getBounds().equals(getBounds())) {
+                mStartingData.mResizedFromTransfer = true;
+                // Lock the starting window to task, so it won't resize from transfer anymore.
+                mActivityRecord.associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
@@ -3907,7 +3916,7 @@
      * LetterboxUiController#shouldShowLetterboxUi} for more context.
      */
     boolean areAppWindowBoundsLetterboxed() {
-        return mActivityRecord != null
+        return mActivityRecord != null && !isStartingWindowAssociatedToTask()
                 && (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
     }
 
@@ -5673,6 +5682,12 @@
             // 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
         // since a generic WindowContainer only needs to wait for its
         // children to finish and is immediately ready from its own
diff --git a/services/core/jni/TEST_MAPPING b/services/core/jni/TEST_MAPPING
new file mode 100644
index 0000000..ea44d06
--- /dev/null
+++ b/services/core/jni/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+  "presubmit": [
+    {
+      "file_patterns": [
+        "[^/]*(vibrator)[^/]*\\.[^/]*"
+      ],
+      "name": "CtsVibratorTestCases",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ]
+}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index c0cfa53..486ddb4 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -420,14 +420,11 @@
             setDeviceServer(server);
         }
 
-        @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
-                Manifest.permission.CREATE_USERS,
-                Manifest.permission.MANAGE_USERS})
         public Device(BluetoothDevice bluetoothDevice) {
             mBluetoothDevice = bluetoothDevice;
             mServiceInfo = null;
             mUid = mBluetoothServiceUid;
-            mUserId = mUserManager.getMainUser().getIdentifier();
+            mUserId = UserHandle.getUserId(mUid);
         }
 
         private void setDeviceServer(IMidiDeviceServer server) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 17474fb..6a349e2 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -26,6 +26,7 @@
 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.immutable.IndexedMap
 import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
@@ -46,6 +47,7 @@
                 getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
             }
             addPolicy(AppIdPermissionPolicy())
+            addPolicy(DevicePermissionPolicy())
             addPolicy(AppIdAppOpPolicy())
             addPolicy(PackageAppOpPolicy())
         } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 4ec32ea..94c878a 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -329,6 +329,18 @@
 private typealias AppIdPermissionFlagsReference =
     MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
 
+
+typealias DevicePermissionFlags =
+    IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias MutableDevicePermissionFlags =
+    MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
+typealias AppIdDevicePermissionFlags =
+    IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+typealias MutableAppIdDevicePermissionFlags =
+    MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
+private typealias AppIdDevicePermissionFlagsReference =
+    MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags>
+
 typealias AppIdAppOpModes =
     IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
 typealias MutableAppIdAppOpModes =
@@ -346,6 +358,7 @@
 sealed class UserState(
     internal val packageVersionsReference: PackageVersionsReference,
     internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    internal val appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
     internal val appIdAppOpModesReference: AppIdAppOpModesReference,
     internal val packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
@@ -357,6 +370,9 @@
     val appIdPermissionFlags: AppIdPermissionFlags
         get() = appIdPermissionFlagsReference.get()
 
+    val appIdDevicePermissionFlags: AppIdDevicePermissionFlags
+        get() = appIdDevicePermissionFlagsReference.get()
+
     val appIdAppOpModes: AppIdAppOpModes
         get() = appIdAppOpModesReference.get()
 
@@ -375,6 +391,7 @@
 class MutableUserState private constructor(
     packageVersionsReference: PackageVersionsReference,
     appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
+    appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
     appIdAppOpModesReference: AppIdAppOpModesReference,
     packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
@@ -382,6 +399,7 @@
 ) : UserState(
     packageVersionsReference,
     appIdPermissionFlagsReference,
+    appIdDevicePermissionFlagsReference,
     appIdAppOpModesReference,
     packageAppOpModesReference,
     defaultPermissionGrantFingerprint,
@@ -390,6 +408,7 @@
     constructor() : this(
         PackageVersionsReference(MutableIndexedMap<String, Int>()),
         AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
+        AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
         AppIdAppOpModesReference(MutableAppIdAppOpModes()),
         PackageAppOpModesReference(MutablePackageAppOpModes()),
         null,
@@ -399,6 +418,7 @@
     internal constructor(userState: UserState) : this(
         userState.packageVersionsReference.toImmutable(),
         userState.appIdPermissionFlagsReference.toImmutable(),
+        userState.appIdDevicePermissionFlagsReference.toImmutable(),
         userState.appIdAppOpModesReference.toImmutable(),
         userState.packageAppOpModesReference.toImmutable(),
         userState.defaultPermissionGrantFingerprint,
@@ -410,6 +430,9 @@
     fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags =
         appIdPermissionFlagsReference.mutate()
 
+    fun mutateAppIdDevicePermissionFlags(): MutableAppIdDevicePermissionFlags =
+        appIdDevicePermissionFlagsReference.mutate()
+
     fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate()
 
     fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index d1abc04..1d46ca7 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -65,6 +65,17 @@
     }
 }
 
+data class DevicePermissionUri(
+    val permissionName: String,
+    val deviceId: Int
+) : AccessUri(SCHEME) {
+    override fun toString(): String = "$scheme:///$permissionName/$deviceId"
+
+    companion object {
+        const val SCHEME = "device-permission"
+    }
+}
+
 data class UidUri(
     val uid: Int
 ) : AccessUri(SCHEME) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
index 6108ad2..ce4aa44 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -16,6 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Immutable list with index-based access.
+ */
 sealed class IndexedList<T>(
     internal val list: ArrayList<T>
 ) : Immutable<MutableIndexedList<T>> {
@@ -34,6 +37,9 @@
     override fun toString(): String = list.toString()
 }
 
+/**
+ * Mutable list with index-based access.
+ */
 class MutableIndexedList<T>(
     list: ArrayList<T> = ArrayList()
 ) : IndexedList<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
index 1202c81..77e71ba 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -16,6 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Immutable set with index-based access, implemented using a list.
+ */
 sealed class IndexedListSet<T>(
     internal val list: ArrayList<T>
 ) : Immutable<MutableIndexedListSet<T>> {
@@ -36,6 +39,9 @@
     override fun toString(): String = list.toString()
 }
 
+/**
+ * Mutable set with index-based access, implemented using a list.
+ */
 class MutableIndexedListSet<T>(
     list: ArrayList<T> = ArrayList()
 ) : IndexedListSet<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
index 5c75de8..299cc89 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -18,6 +18,9 @@
 
 import android.util.ArrayMap
 
+/**
+ * Immutable map with index-based access.
+ */
 sealed class IndexedMap<K, V>(
     internal val map: ArrayMap<K, V>
 ) : Immutable<MutableIndexedMap<K, V>> {
@@ -42,6 +45,9 @@
     override fun toString(): String = map.toString()
 }
 
+/**
+ * Mutable map with index-based access.
+ */
 class MutableIndexedMap<K, V>(
     map: ArrayMap<K, V> = ArrayMap()
 ) : IndexedMap<K, V>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
index 8c963aa..ff76a47 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -18,6 +18,11 @@
 
 import android.util.ArrayMap
 
+/**
+ * Immutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>(
     internal val map: ArrayMap<K, MutableReference<I, M>>
 ) : Immutable<MutableIndexedReferenceMap<K, I, M>> {
@@ -42,6 +47,11 @@
     override fun toString(): String = map.toString()
 }
 
+/**
+ * Mutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
     map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
 ) : IndexedReferenceMap<K, I, M>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
index 9868616..547e56c 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -18,6 +18,9 @@
 
 import android.util.ArraySet
 
+/**
+ * Immutable set with index-based access.
+ */
 sealed class IndexedSet<T>(
     internal val set: ArraySet<T>
 ) : Immutable<MutableIndexedSet<T>> {
@@ -37,6 +40,9 @@
     override fun toString(): String = set.toString()
 }
 
+/**
+ * Mutable set with index-based access.
+ */
 class MutableIndexedSet<T>(
     set: ArraySet<T> = ArraySet()
 ) : IndexedSet<T>(set) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
index b7d8b4c..7ed29e8 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -18,6 +18,9 @@
 
 import android.util.SparseArray
 
+/**
+ * Immutable map with index-based access and [Int] keys.
+ */
 sealed class IntMap<T>(
     internal val array: SparseArray<T>
 ) : Immutable<MutableIntMap<T>> {
@@ -41,6 +44,9 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable map with index-based access and [Int] keys.
+ */
 class MutableIntMap<T>(
     array: SparseArray<T> = SparseArray()
 ) : IntMap<T>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
index 22fa8f2..160b227 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -18,6 +18,11 @@
 
 import android.util.SparseArray
 
+/**
+ * Immutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 sealed class IntReferenceMap<I : Immutable<M>, M : I>(
     internal val array: SparseArray<MutableReference<I, M>>
 ) : Immutable<MutableIntReferenceMap<I, M>> {
@@ -42,6 +47,11 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 class MutableIntReferenceMap<I : Immutable<M>, M : I>(
     array: SparseArray<MutableReference<I, M>> = SparseArray()
 ) : IntReferenceMap<I, M>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
index 9da3671..21f2af2 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -18,6 +18,9 @@
 
 import android.util.SparseBooleanArray
 
+/**
+ * Immutable set with index-based access and [Int] elements.
+ */
 sealed class IntSet(
     internal val array: SparseBooleanArray
 ) : Immutable<MutableIntSet> {
@@ -37,6 +40,9 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable set with index-based access and [Int] elements.
+ */
 class MutableIntSet(
     array: SparseBooleanArray = SparseBooleanArray()
 ) : IntSet(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
index e39a3bb..171cfeb 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -16,14 +16,39 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Wrapper class for reference to a mutable data structure instance.
+ *
+ * This class encapsulates the logic to mutate/copy a mutable data structure instance and update the
+ * reference to the new mutated instance. It also remembers the mutated instance so that it can be
+ * reused during further mutations.
+ *
+ * Instances of this class should be kept private within a data structure, with the [get] method
+ * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate]
+ * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the
+ * data structure is mutated/copied, a new instance of this class should be obtained with
+ * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents
+ * further modifications to a data structure accessed with its immutable interface.
+ *
+ * @see MutableIndexedReferenceMap
+ * @see MutableIntReferenceMap
+ */
 class MutableReference<I : Immutable<M>, M : I> private constructor(
     private var immutable: I,
     private var mutable: M?
 ) {
     constructor(mutable: M) : this(mutable, mutable)
 
+    /**
+     * Return an immutable reference to the wrapped mutable data structure.
+     */
     fun get(): I = immutable
 
+    /**
+     * Make the wrapped mutable data structure mutable, by either calling [Immutable.toMutable] and
+     * replacing the wrapped reference with its result, or reusing the existing reference if it's
+     * already mutable.
+     */
     fun mutate(): M {
         mutable?.let { return it }
         return immutable.toMutable().also {
@@ -32,6 +57,10 @@
         }
     }
 
+    /**
+     * Create a new [MutableReference] instance with the wrapped mutable data structure being
+     * immutable-only again.
+     */
     fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
 
     override fun equals(other: Any?): Boolean {
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
new file mode 100644
index 0000000..37a4a90
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.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.server.permission.access.permission
+
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.DevicePermissionFlags
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutableAppIdDevicePermissionFlags
+import com.android.server.permission.access.MutableDevicePermissionFlags
+import com.android.server.permission.access.WriteMode
+import com.android.server.permission.access.immutable.IndexedMap
+import com.android.server.permission.access.immutable.MutableIndexedMap
+import com.android.server.permission.access.immutable.forEachIndexed
+import com.android.server.permission.access.immutable.forEachReversedIndexed
+import com.android.server.permission.access.immutable.set
+import com.android.server.permission.access.util.andInv
+import com.android.server.permission.access.util.attributeInt
+import com.android.server.permission.access.util.attributeInterned
+import com.android.server.permission.access.util.forEachTag
+import com.android.server.permission.access.util.getAttributeIntOrThrow
+import com.android.server.permission.access.util.getAttributeValueOrThrow
+import com.android.server.permission.access.util.hasBits
+import com.android.server.permission.access.util.tag
+import com.android.server.permission.access.util.tagName
+
+class DevicePermissionPersistence {
+    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+        when (tagName) {
+            TAG_APP_ID_DEVICE_PERMISSIONS -> parseAppIdDevicePermissions(state, userId)
+            else -> {}
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppIdDevicePermissions(
+        state: MutableAccessState,
+        userId: Int
+    ) {
+        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+        val appIdDevicePermissionFlags = userState.mutateAppIdDevicePermissionFlags()
+        forEachTag {
+            when (tagName) {
+                TAG_APP_ID -> parseAppId(appIdDevicePermissionFlags)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+            }
+        }
+
+        appIdDevicePermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
+            if (appId !in state.externalState.appIdPackageNames) {
+                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
+                appIdDevicePermissionFlags.removeAt(appIdIndex)
+                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseAppId(
+        appIdPermissionFlags: MutableAppIdDevicePermissionFlags
+    ) {
+        val appId = getAttributeIntOrThrow(ATTR_ID)
+        val devicePermissionFlags = MutableDevicePermissionFlags()
+        appIdPermissionFlags[appId] = devicePermissionFlags
+        forEachTag {
+            when (tagName) {
+                TAG_DEVICE -> parseDevice(devicePermissionFlags)
+                else -> {
+                    Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+                }
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parseDevice(
+        deviceIdPermissionFlags: MutableDevicePermissionFlags
+    ) {
+        val deviceId = getAttributeValueOrThrow(ATTR_ID)
+        val permissionFlags = MutableIndexedMap<String, Int>()
+        deviceIdPermissionFlags.put(deviceId, permissionFlags)
+        forEachTag {
+            when (tagName) {
+                TAG_PERMISSION -> parsePermission(permissionFlags)
+                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
+            }
+        }
+    }
+
+    private fun BinaryXmlPullParser.parsePermission(
+        permissionFlags: MutableIndexedMap<String, Int>
+    ) {
+        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
+        val flags = getAttributeIntOrThrow(ATTR_FLAGS)
+        permissionFlags[name] = flags
+    }
+
+    fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+        val appIdDevicePermissionFlags = state.userStates[userId]!!.appIdDevicePermissionFlags
+        tag(TAG_APP_ID_DEVICE_PERMISSIONS) {
+            appIdDevicePermissionFlags.forEachIndexed { _, appId, devicePermissionFlags ->
+                serializeAppId(appId, devicePermissionFlags)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeAppId(
+        appId: Int,
+        devicePermissionFlags: DevicePermissionFlags
+    ) {
+        tag(TAG_APP_ID) {
+            attributeInt(ATTR_ID, appId)
+            devicePermissionFlags.forEachIndexed { _, deviceId, permissionFlags ->
+                serializeDevice(deviceId, permissionFlags)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializeDevice(
+        deviceId: String,
+        permissionFlags: IndexedMap<String, Int>
+    ) {
+        tag(TAG_DEVICE) {
+            attributeInterned(ATTR_ID, deviceId)
+            permissionFlags.forEachIndexed { _, name, flags ->
+                serializePermission(name, flags)
+            }
+        }
+    }
+
+    private fun BinaryXmlSerializer.serializePermission(name: String, flags: Int) {
+        tag(TAG_PERMISSION) {
+            attributeInterned(ATTR_NAME, name)
+            // Never serialize one-time permissions as granted.
+            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
+                flags andInv PermissionFlags.RUNTIME_GRANTED
+            } else {
+                flags
+            }
+            attributeInt(ATTR_FLAGS, serializedFlags)
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = DevicePermissionPersistence::class.java.simpleName
+
+        private const val TAG_APP_ID_DEVICE_PERMISSIONS = "app-id-device-permissions"
+        private const val TAG_APP_ID = "app-id"
+        private const val TAG_DEVICE = "device"
+        private const val TAG_PERMISSION = "permission"
+
+        private const val ATTR_ID = "id"
+        private const val ATTR_NAME = "name"
+        private const val ATTR_FLAGS = "flags"
+    }
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
new file mode 100644
index 0000000..c0d7546
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.permission.access.permission
+
+import android.util.Slog
+import com.android.modules.utils.BinaryXmlPullParser
+import com.android.modules.utils.BinaryXmlSerializer
+import com.android.server.permission.access.AccessState
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutableAccessState
+import com.android.server.permission.access.MutateStateScope
+import com.android.server.permission.access.SchemePolicy
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
+import com.android.server.permission.access.util.andInv
+import com.android.server.pm.pkg.PackageState
+
+class DevicePermissionPolicy : SchemePolicy() {
+    private val persistence = DevicePermissionPersistence()
+
+    @Volatile
+    private var listeners: IndexedListSet<OnDevicePermissionFlagsChangedListener> =
+        MutableIndexedListSet()
+    private val listenersLock = Any()
+
+    override val subjectScheme: String
+        get() = UidUri.SCHEME
+
+    override val objectScheme: String
+        get() = DevicePermissionUri.SCHEME
+
+    override fun GetStateScope.onStateMutated() {
+        listeners.forEachIndexed { _, it -> it.onStateMutated() }
+    }
+
+    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
+        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
+            if (appId in userState.appIdDevicePermissionFlags) {
+                newState.mutateUserStateAt(userStateIndex)
+                    .mutateAppIdDevicePermissionFlags() -= appId
+            }
+        }
+    }
+
+    override fun MutateStateScope.onStorageVolumeMounted(
+        volumeUuid: String?,
+        packageNames: List<String>,
+        isSystemUpdated: Boolean
+    ) {
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = newState.externalState.packageStates[packageName]!!
+            trimPermissionStates(packageState.appId)
+        }
+    }
+
+    override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
+        trimPermissionStates(packageState.appId)
+    }
+
+    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
+        if (appId in newState.externalState.appIdPackageNames) {
+            trimPermissionStates(appId)
+        }
+    }
+
+    override fun MutateStateScope.onPackageUninstalled(
+        packageName: String,
+        appId: Int,
+        userId: Int
+    ) {
+        resetPermissionStates(packageName, userId)
+    }
+
+    private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) {
+        // It's okay to skip resetting permissions for packages that are removed,
+        // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
+        val packageState = newState.externalState.packageStates[packageName] ?: return
+        val androidPackage = packageState.androidPackage ?: return
+        val appId = packageState.appId
+        val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+        androidPackage.requestedPermissions.forEach { permissionName ->
+            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
+                it.packageName != packageName &&
+                    permissionName in it.androidPackage!!.requestedPermissions
+            }
+            if (isRequestedByOtherPackages) {
+                return@forEach
+            }
+            appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+                setPermissionFlags(appId, deviceId, userId, permissionName, 0)
+            }
+        }
+    }
+
+    private fun MutateStateScope.trimPermissionStates(appId: Int) {
+        val requestedPermissions = MutableIndexedSet<String>()
+        forEachPackageInAppId(appId) {
+            requestedPermissions += it.androidPackage!!.requestedPermissions
+        }
+        newState.userStates.forEachIndexed { _, userId, userState ->
+            userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed {
+                    _, deviceId, permissionFlags ->
+                permissionFlags.forEachReversedIndexed { _, permissionName, _ ->
+                    if (permissionName !in requestedPermissions) {
+                        setPermissionFlags(appId, deviceId, userId, permissionName, 0)
+                    }
+                }
+            }
+        }
+    }
+
+    private inline fun MutateStateScope.anyPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        predicate: (PackageState) -> Boolean
+    ): Boolean {
+        val packageNames = state.externalState.appIdPackageNames[appId]!!
+        return packageNames.anyIndexed { _, packageName ->
+            val packageState = state.externalState.packageStates[packageName]!!
+            packageState.androidPackage != null && predicate(packageState)
+        }
+    }
+
+    private inline fun MutateStateScope.forEachPackageInAppId(
+        appId: Int,
+        state: AccessState = newState,
+        action: (PackageState) -> Unit
+    ) {
+        val packageNames = state.externalState.appIdPackageNames[appId]!!
+        packageNames.forEachIndexed { _, packageName ->
+            val packageState = state.externalState.packageStates[packageName]!!
+            if (packageState.androidPackage != null) {
+                action(packageState)
+            }
+        }
+    }
+
+    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
+        with(persistence) { this@parseUserState.parseUserState(state, userId) }
+    }
+
+    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
+        with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
+    }
+
+    fun GetStateScope.getPermissionFlags(
+        appId: Int,
+        deviceId: String,
+        userId: Int,
+        permissionName: String
+    ): Int =
+        state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId)
+            ?.getWithDefault(permissionName, 0) ?: 0
+
+    fun MutateStateScope.setPermissionFlags(
+        appId: Int,
+        deviceId: String,
+        userId: Int,
+        permissionName: String,
+        flags: Int
+    ): Boolean =
+        updatePermissionFlags(
+            appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags
+        )
+
+    private fun MutateStateScope.updatePermissionFlags(
+        appId: Int,
+        deviceId: String,
+        userId: Int,
+        permissionName: String,
+        flagMask: Int,
+        flagValues: Int
+    ): Boolean {
+        if (!isDeviceAwarePermission(permissionName)) {
+            Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
+            return false
+        }
+        val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId]
+            ?.get(deviceId).getWithDefault(permissionName, 0)
+        val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
+        if (oldFlags == newFlags) {
+            return false
+        }
+        val appIdDevicePermissionFlags =
+            newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
+        val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) {
+            MutableIndexedReferenceMap()
+        }
+        val permissionFlags = devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() }
+        permissionFlags.putWithDefault(permissionName, newFlags, 0)
+        if (permissionFlags.isEmpty()) {
+            devicePermissionFlags -= deviceId
+            if (devicePermissionFlags.isEmpty()) {
+                appIdDevicePermissionFlags -= appId
+            }
+        }
+        listeners.forEachIndexed { _, it ->
+            it.onDevicePermissionFlagsChanged(
+                appId, userId, deviceId, permissionName, oldFlags, newFlags
+            )
+        }
+        return true
+    }
+
+    fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
+        synchronized(listenersLock) {
+            listeners = listeners + listener
+        }
+    }
+
+    fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
+        synchronized(listenersLock) {
+            listeners = listeners - listener
+        }
+    }
+
+    private fun isDeviceAwarePermission(permissionName: String): Boolean =
+        DEVICE_SUPPORTED_PERMISSIONS.contains(permissionName)
+
+    companion object {
+        private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
+
+        /**
+         * These permissions are supported for virtual devices.
+         */
+        private val DEVICE_SUPPORTED_PERMISSIONS = indexedSetOf(
+            android.Manifest.permission.CAMERA,
+            android.Manifest.permission.RECORD_AUDIO
+        )
+    }
+
+    /**
+     * TODO: b/289355341 - implement listener for permission changes
+     * Listener for permission flags changes.
+     */
+    abstract class OnDevicePermissionFlagsChangedListener {
+        /**
+         * Called when a permission flags change has been made to the upcoming new state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation,
+         * and only call external code after [onStateMutated] when the new state has actually become
+         * the current state visible to external code.
+         */
+        abstract fun onDevicePermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            deviceId: String,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        )
+
+        /**
+         * Called when the upcoming new state has become the current state.
+         *
+         * Implementations should keep this method fast to avoid stalling the locked state mutation.
+         */
+        abstract fun onStateMutated()
+    }
+}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index edacf188..d9f179a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -64,9 +64,11 @@
 import com.android.server.PermissionThread
 import com.android.server.ServiceThread
 import com.android.server.SystemConfig
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PermissionUri
@@ -110,6 +112,9 @@
     private val policy =
         service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
 
+    private val devicePolicy =
+        service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
+
     private val context = service.context
     private lateinit var metricsLogger: MetricsLogger
     private lateinit var packageManagerInternal: PackageManagerInternal
@@ -132,6 +137,8 @@
 
     private lateinit var permissionControllerManager: PermissionControllerManager
 
+    private lateinit var virtualDeviceManagerInternal: VirtualDeviceManagerInternal
+
     /**
      * A permission backup might contain apps that are not installed. In this case we delay the
      * restoration until the app is installed.
@@ -152,6 +159,8 @@
         systemConfig = SystemConfig.getInstance()
         userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
         userManagerService = UserManagerService.getInstance()
+        virtualDeviceManagerInternal =
+            LocalServices.getService(VirtualDeviceManagerInternal::class.java)
 
         // The package info cache is the cache for package and permission information.
         // Disable the package info and package permission caches locally but leave the
@@ -460,7 +469,7 @@
         return size
     }
 
-    override fun checkUidPermission(uid: Int, permissionName: String): Int {
+    override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int {
         val userId = UserHandle.getUserId(uid)
         if (!userManagerInternal.exists(userId)) {
             return PackageManager.PERMISSION_DENIED
@@ -482,7 +491,7 @@
                 return PackageManager.PERMISSION_DENIED
             }
             val isPermissionGranted = service.getState {
-                isPermissionGranted(packageState, userId, permissionName)
+                isPermissionGranted(packageState, userId, permissionName, deviceId)
             }
             return if (isPermissionGranted) {
                 PackageManager.PERMISSION_GRANTED
@@ -515,7 +524,12 @@
         return false
     }
 
-    override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+    override fun checkPermission(
+        packageName: String,
+        permissionName: String,
+        deviceId: Int,
+        userId: Int
+    ): Int {
         if (!userManagerInternal.exists(userId)) {
             return PackageManager.PERMISSION_DENIED
         }
@@ -524,7 +538,7 @@
             .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
 
         val isPermissionGranted = service.getState {
-            isPermissionGranted(packageState, userId, permissionName)
+            isPermissionGranted(packageState, userId, permissionName, deviceId)
         }
         return if (isPermissionGranted) {
             PackageManager.PERMISSION_GRANTED
@@ -542,19 +556,21 @@
     private fun GetStateScope.isPermissionGranted(
         packageState: PackageState,
         userId: Int,
-        permissionName: String
+        permissionName: String,
+        deviceId: Int
     ): Boolean {
         val appId = packageState.appId
         // Note that instant apps can't have shared UIDs, so we only need to check the current
         // package state.
         val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp
-        if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) {
+        if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) {
             return true
         }
 
         val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
         if (fullerPermissionName != null &&
-            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) {
+            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId)
+        ) {
             return true
         }
 
@@ -568,9 +584,10 @@
         appId: Int,
         userId: Int,
         isInstantApp: Boolean,
-        permissionName: String
+        permissionName: String,
+        deviceId: Int,
     ): Boolean {
-        val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
         if (!PermissionFlags.isPermissionGranted(flags)) {
             return false
         }
@@ -601,7 +618,8 @@
                 ?: return emptySet()
 
             return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
-                if (isPermissionGranted(packageState, userId, permissionName)) {
+                if (isPermissionGranted(
+                        packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) {
                     permissionName
                 } else {
                     null
@@ -640,18 +658,26 @@
         }
     }
 
-    override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
-        setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true)
+    override fun grantRuntimePermission(
+        packageName: String,
+        permissionName: String,
+        deviceId: Int,
+        userId: Int
+    ) {
+        setRuntimePermissionGranted(
+            packageName, userId, permissionName, deviceId, isGranted = true
+        )
     }
 
     override fun revokeRuntimePermission(
         packageName: String,
         permissionName: String,
+        deviceId: Int,
         userId: Int,
         reason: String?
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, permissionName, isGranted = false, revokeReason = reason
+            packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason
         )
     }
 
@@ -660,8 +686,8 @@
         userId: Int
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false,
-            skipKillUid = true
+            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT,
+            isGranted = false, skipKillUid = true
         )
     }
 
@@ -673,6 +699,7 @@
         packageName: String,
         userId: Int,
         permissionName: String,
+        deviceId: Int,
         isGranted: Boolean,
         skipKillUid: Boolean = false,
         revokeReason: String? = null
@@ -748,7 +775,7 @@
             }
 
             setRuntimePermissionGranted(
-                packageState, userId, permissionName, isGranted, canManageRolePermission,
+                packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission,
                 overridePolicyFixed, reportError = true, methodName
             )
         }
@@ -782,14 +809,16 @@
                         if (permissionState ==
                             PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
                             setRuntimePermissionGranted(
-                                packageState, userId, permissionName, isGranted = true,
-                                canManageRolePermission = false, overridePolicyFixed = false,
-                                reportError = false, "setRequestedPermissionStates"
+                                packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT,
+                                isGranted = true, canManageRolePermission = false,
+                                overridePolicyFixed = false, reportError = false,
+                                "setRequestedPermissionStates"
                             )
                             updatePermissionFlags(
                                 packageState.appId, userId, permissionName,
+                                Context.DEVICE_ID_DEFAULT,
                                 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
-                                    PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+                                PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
                                 canUpdateSystemFlags = false,
                                 reportErrorForUnknownPermission = false,
                                 isPermissionRequested = true, "setRequestedPermissionStates",
@@ -816,6 +845,7 @@
         packageState: PackageState,
         userId: Int,
         permissionName: String,
+        deviceId: Int,
         isGranted: Boolean,
         canManageRolePermission: Boolean,
         overridePolicyFixed: Boolean,
@@ -871,7 +901,7 @@
         }
 
         val appId = packageState.appId
-        val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
 
         if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
             if (reportError) {
@@ -934,7 +964,7 @@
             return
         }
 
-        with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+        setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
 
         if (permission.isRuntime) {
             val action = if (isGranted) {
@@ -963,7 +993,12 @@
         with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
     }
 
-    override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
+    override fun getPermissionFlags(
+        packageName: String,
+        permissionName: String,
+        deviceId: Int,
+        userId: Int,
+    ): Int {
         if (!userManagerInternal.exists(userId)) {
             Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
             return 0
@@ -994,7 +1029,8 @@
             }
 
             val flags =
-                with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
+                getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
+
             return PermissionFlags.toApiFlags(flags)
         }
     }
@@ -1002,6 +1038,7 @@
     override fun isPermissionRevokedByPolicy(
         packageName: String,
         permissionName: String,
+        deviceId: Int,
         userId: Int
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
@@ -1018,13 +1055,13 @@
             .use { it.getPackageState(packageName) } ?: return false
 
         service.getState {
-            if (isPermissionGranted(packageState, userId, permissionName)) {
+            if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
                 return false
             }
 
-            val flags = with(policy) {
-                getPermissionFlags(packageState.appId, userId, permissionName)
-            }
+            val flags =
+                getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
+
             return flags.hasBits(PermissionFlags.POLICY_FIXED)
         }
     }
@@ -1046,7 +1083,8 @@
     override fun shouldShowRequestPermissionRationale(
         packageName: String,
         permissionName: String,
-        userId: Int
+        deviceId: Int,
+        userId: Int,
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
             Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
@@ -1068,11 +1106,11 @@
 
         val flags: Int
         service.getState {
-            if (isPermissionGranted(packageState, userId, permissionName)) {
+            if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
                 return false
             }
 
-            flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+            flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
         }
         if (flags.hasAnyBit(UNREQUESTABLE_MASK)) {
             return false
@@ -1104,6 +1142,7 @@
         flagMask: Int,
         flagValues: Int,
         enforceAdjustPolicyPermission: Boolean,
+        deviceId: Int,
         userId: Int
     ) {
         val callingUid = Binder.getCallingUid()
@@ -1199,7 +1238,7 @@
         val appId = packageState.appId
         service.mutateState {
             updatePermissionFlags(
-                appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags,
+                appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags,
                 reportErrorForUnknownPermission = true, isPermissionRequested,
                 "updatePermissionFlags", packageName
             )
@@ -1248,8 +1287,9 @@
                 val androidPackage = packageState.androidPackage ?: return@forEach
                 androidPackage.requestedPermissions.forEach { permissionName ->
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName, flagMask, flagValues,
-                        canUpdateSystemFlags, reportErrorForUnknownPermission = false,
+                        packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT,
+                        flagMask, flagValues, canUpdateSystemFlags,
+                        reportErrorForUnknownPermission = false,
                         isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
                     )
                 }
@@ -1264,6 +1304,7 @@
         appId: Int,
         userId: Int,
         permissionName: String,
+        deviceId: Int,
         flagMask: Int,
         flagValues: Int,
         canUpdateSystemFlags: Boolean,
@@ -1298,7 +1339,7 @@
             return
         }
 
-        val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
         if (!isPermissionRequested && oldFlags == 0) {
             Slog.w(
                 LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
@@ -1308,7 +1349,7 @@
         }
 
         val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues)
-        with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
+        setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
     }
 
     override fun getAllowlistedRestrictedPermissions(
@@ -1365,6 +1406,49 @@
         )
     }
 
+    private fun GetStateScope.getPermissionFlagsWithPolicy(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        deviceId: Int,
+    ): Int =
+        if (deviceId == Context.DEVICE_ID_DEFAULT) {
+            with(policy) { getPermissionFlags(appId, userId, permissionName) }
+        } else {
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            if (persistentDeviceId != null) {
+                with(devicePolicy) {
+                    getPermissionFlags(appId, persistentDeviceId, userId, permissionName)
+                }
+            } else {
+                Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no persistent device ID found.")
+                0
+            }
+        }
+
+    private fun MutateStateScope.setPermissionFlagsWithPolicy(
+        appId: Int,
+        userId: Int,
+        permissionName: String,
+        deviceId: Int,
+        flags: Int
+    ): Boolean =
+        if (deviceId == Context.DEVICE_ID_DEFAULT) {
+            with(policy) {
+                setPermissionFlags(appId, userId, permissionName, flags)
+            }
+        } else {
+            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
+            if (persistentDeviceId != null) {
+                with(devicePolicy) {
+                    setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags)
+                }
+            } else {
+                Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no cdm association found.")
+                false
+            }
+        }
+
     /**
      * This method does not enforce checks on the caller, should only be called after
      * required checks.
@@ -1539,8 +1623,7 @@
     ) {
         service.mutateState {
             with(policy) {
-                val permissionsFlags =
-                    getUidPermissionFlags(appId, userId) ?: return@mutateState
+                val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
 
                 val permissions = getPermissions()
                 androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
@@ -1661,8 +1744,6 @@
         )
     }
 
-
-
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
         requireNotNull(permissionName) { "permissionName cannot be null" }
         val packageNames = ArraySet<String>()
@@ -1879,7 +1960,7 @@
                     println("Permissions:")
                     withIndent {
                         userState.appIdPermissionFlags[appId]?.forEachIndexed {
-                                _, permissionName, flags ->
+                            _, permissionName, flags ->
                             val isGranted = PermissionFlags.isPermissionGranted(flags)
                             println(
                                 "$permissionName: granted=$isGranted, flags=" +
@@ -1888,6 +1969,20 @@
                         }
                     }
 
+                    userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
+                            _, deviceId, devicePermissionFlags ->
+                        println("Permissions (Device $deviceId):")
+                        withIndent {
+                            devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
+                                val isGranted = PermissionFlags.isPermissionGranted(flags)
+                                println(
+                                    "$permissionName: granted=$isGranted, flags=" +
+                                        PermissionFlags.toString(flags)
+                                )
+                            }
+                        }
+                    }
+
                     println("App ops:")
                     withIndent {
                         userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
@@ -2412,7 +2507,7 @@
         }
 
         private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
-            if (checkUidPermission(uid, Manifest.permission.BACKUP) !=
+            if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
                 PackageManager.PERMISSION_GRANTED) {
                 return false
             }
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 35b9bc3..4a8d73d2 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -254,12 +254,45 @@
             }
             final long identity = Binder.clearCallingIdentity();
             try {
-                return userState.getCustomPrinterIcon(printerId);
+                Icon icon = userState.getCustomPrinterIcon(printerId);
+                return validateIconUserBoundary(icon);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
         }
 
+        /**
+         * Validates the custom printer icon to see if it's not in the calling user space.
+         * If the condition is not met, return null. Otherwise, return the original icon.
+         *
+         * @param icon
+         * @return icon (validated)
+         */
+        private Icon validateIconUserBoundary(Icon icon) {
+            // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+            // incompatible types.
+            if (icon != null && (icon.getType() == Icon.TYPE_URI
+                    || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+                String encodedUser = icon.getUri().getEncodedUserInfo();
+
+                // If there is no encoded user, the URI is calling into the calling user space
+                if (encodedUser != null) {
+                    int userId = Integer.parseInt(encodedUser);
+                    // resolve encoded user
+                    final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+
+                    synchronized (mLock) {
+                        // Only the current group members can get the printer icons.
+                        if (resolveCallingProfileParentLocked(resolvedUserId)
+                                != getCurrentUserId()) {
+                            return null;
+                        }
+                    }
+                }
+            }
+            return icon;
+        }
+
         @Override
         public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) {
             if (printJobId == null) {
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
index b698a60..8ef443e 100644
--- a/services/tests/dreamservicetests/Android.bp
+++ b/services/tests/dreamservicetests/Android.bp
@@ -16,6 +16,7 @@
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
         "services.core",
+        "mockingservicestests-utils-mockito",
     ],
 
     platform_apis: true,
diff --git a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
similarity index 97%
rename from services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
rename to services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
index c02cbd1..32d4e75 100644
--- a/services/tests/mockingservicestests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -36,6 +36,7 @@
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -102,6 +103,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 293443309)
     public void testSettingsQueryUserChange() {
         final DreamManagerService service = createService();
 
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 1f4563f..976e740 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -105,6 +105,7 @@
 import com.android.server.wm.ActivityTaskManagerService;
 import com.android.server.wm.WindowProcessController;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -206,8 +207,10 @@
         setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
         doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
                 .enqueueProcessChangeItemLocked(anyInt(), anyInt());
-        sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
-                new ActiveUids(sService, false));
+        sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(sService, sService.mProcessList,
+                        new ActiveUids(sService, false))
+                : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false));
         sService.mOomAdjuster.mAdjSeq = 10000;
         sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
         if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
@@ -220,6 +223,11 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
     }
 
+    @After
+    public void tearDown() {
+        sService.mOomAdjuster.resetInternal();
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
@@ -249,6 +257,9 @@
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         Collections.addAll(lru, apps);
+        for (ProcessRecord app : apps) {
+            sService.mOomAdjuster.onProcessBeginLocked(app);
+        }
     }
 
     /**
@@ -259,6 +270,7 @@
     @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
+            sService.mOomAdjuster.onProcessBeginLocked(apps[0]);
             sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
@@ -600,10 +612,13 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         // Out of grace period but valid binding allows the adjustment.
@@ -620,10 +635,13 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs =
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         // Out of grace period and no valid binding so no adjustment.
@@ -641,10 +659,13 @@
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
     }
 
@@ -657,11 +678,12 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
         system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         system.mState.setHasTopUi(true);
+        sService.mOomAdjuster.onProcessBeginLocked(system);
         // Simulate the system starting and binding to a service in the app.
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        updateOomAdj(system, app);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -850,6 +872,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        client.mServices.setTreatLikeActivity(true);
         bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
                 | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -1006,7 +1029,7 @@
         bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
         assertNoBfsl(app);
@@ -1132,7 +1155,7 @@
         assertNoBfsl(app);
 
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
         assertBfsl(app);
@@ -1148,7 +1171,7 @@
         bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
         client.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1199,6 +1222,8 @@
             updateOomAdj(client, app);
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1217,6 +1242,8 @@
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1229,9 +1256,11 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1246,10 +1275,12 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
     }
 
@@ -1849,7 +1880,7 @@
 
         bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
         bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
-        updateOomAdj(app1, app2);
+        updateOomAdj(client1, client2, app1, app2);
 
         assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_TOP_APP);
@@ -1899,6 +1930,8 @@
 
         s1.getConnections().clear();
         s2.getConnections().clear();
+        client1.mServices.removeAllConnections();
+        client2.mServices.removeAllConnections();
         client1.mState.setMaxAdj(UNKNOWN_ADJ);
         client2.mState.setMaxAdj(UNKNOWN_ADJ);
         client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
@@ -1909,7 +1942,7 @@
         bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
                 mock(IBinder.class));
 
-        updateOomAdj(app1, app2);
+        updateOomAdj(client1, client2, app1, app2);
 
         // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
         assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -1922,7 +1955,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
+        updateOomAdj(client2, app2);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -1977,6 +2010,7 @@
         app.setPendingFinishAttach(true);
         app.mState.setHasForegroundActivities(false);
 
+        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -1991,7 +2025,9 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         app.setPendingFinishAttach(true);
         app.mState.setHasForegroundActivities(true);
+        doReturn(app).when(sService).getTopApp();
 
+        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -2088,7 +2124,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client1, client2, app1, app2, app3);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2426,6 +2462,8 @@
         lru.clear();
         lru.add(app2);
         lru.add(app);
+        sService.mOomAdjuster.onProcessBeginLocked(app2);
+        sService.mOomAdjuster.onProcessBeginLocked(app);
 
         final ComponentName cn = ComponentName.unflattenFromString(
                 MOCKAPP_PACKAGENAME + "/.TestService");
@@ -2528,7 +2566,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(app).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
 
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 cd3a78e..6906dec 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2157,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/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log b/services/tests/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log
new file mode 100644
index 0000000..ee331c2
--- /dev/null
+++ b/services/tests/servicestests/res/raw-watch/a11y_three_finger_swipe_down_gesture.log
@@ -0,0 +1,29 @@
+ * Gesture6_id30:Swipe down with 3 finger
+MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=5273700, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_DOWN(1), actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273700, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273709, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_DOWN(2), actionButton=0, id[0]=0, x[0]=150.0, y[0]=696.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=214.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=100.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273709, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=50.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=70.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=100.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273715, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=60.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=80.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=110.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273725, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=70.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=90.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=120.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273734, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=80.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=100.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=130.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273741, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=90.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=110.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=140.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273750, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=100.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=120.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=150.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273758, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=110.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=130.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=160.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273767, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=120.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=140.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=170.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273776, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=130.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=150.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=180.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273784, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=140.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=160.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=190.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273793, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=150.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=170.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=200.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273801, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=160.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=180.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=210.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273810, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=170.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=190.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=220.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273822, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=180.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=200.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=230.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273831, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=190.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=210.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=240.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273836, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=200.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=220.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=250.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273844, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=210.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=230.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=260.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273853, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=220.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=240.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=270.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273862, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=230.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=250.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=280.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273870, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=240.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=260.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=290.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273879, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=150.0, y[0]=250.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=270.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=300.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273888, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_UP(0), actionButton=0, id[0]=0, x[0]=150.0, y[0]=250.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=1, x[1]=216.0, y[1]=270.0, toolType[1]=TOOL_TYPE_FINGER, id[2]=2, x[2]=94.0, y[2]=300.0, toolType[2]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=3, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_POINTER_UP(0), actionButton=0, id[0]=1, x[0]=216.0, y[0]=270.0, toolType[0]=TOOL_TYPE_FINGER, id[1]=2, x[1]=94.0, y[1]=300.0, toolType[1]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=2, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
+MotionEvent { action=ACTION_UP, actionButton=0, id[0]=2, x[0]=94.0, y[0]=300.0, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, classification=NONE, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=5273895, downTime=5273700, deviceId=4, source=0x1002, displayId=0 }
\ No newline at end of file
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..01159b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -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.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/job/BiasSchedulingTest.java b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
index 5e7dc33..1726ec1 100644
--- a/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/BiasSchedulingTest.java
@@ -24,6 +24,8 @@
 import android.os.DeviceIdleManager;
 import android.test.AndroidTestCase;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.server.job.MockBiasJobService.TestEnvironment;
 import com.android.server.job.MockBiasJobService.TestEnvironment.Event;
 
@@ -58,6 +60,7 @@
         super.tearDown();
     }
 
+    @FlakyTest(bugId = 293589359)
     public void testLowerBiasJobPreempted() throws Exception {
         for (int i = 0; i < JobConcurrencyManager.MAX_CONCURRENCY_LIMIT; ++i) {
             JobInfo job = new JobInfo.Builder(100 + i, sJobServiceComponent)
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index cc1100b..e5909a4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -297,13 +298,15 @@
                 TestData.getInsecureCertPathForEndpoint1());
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
 
+        setExpectedScryptArgument(password.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
         KeyDerivationParams keyDerivationParams =
@@ -314,6 +317,44 @@
     }
 
     @Test
+    public void run_zeroizedCredential() throws Exception {
+        String password = TrustedRootCertificates.INSECURE_PASSWORD_PREFIX + "123";
+        String appKeyAlias = TrustedRootCertificates.INSECURE_KEY_ALIAS_PREFIX + "alias";
+        byte[] zeroizedCredential = password.getBytes();
+        mKeySyncTask = new KeySyncTask(
+                mRecoverableKeyStoreDb,
+                mRecoverySnapshotStorage,
+                mSnapshotListenersStorage,
+                TEST_USER_ID,
+                CREDENTIAL_TYPE_PASSWORD,
+                /*credential=*/ zeroizedCredential,
+                /*credentialUpdated=*/ false,
+                mPlatformKeyManager,
+                mTestOnlyInsecureCertificateHelper,
+                mMockScrypt);
+        mRecoverableKeyStoreDb.setServerParams(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_VAULT_HANDLE);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
+        mRecoverableKeyStoreDb.setActiveRootOfTrust(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS);
+        mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
+                TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
+                TrustedRootCertificates.TEST_ONLY_INSECURE_CERTIFICATE_ALIAS,
+                TestData.getInsecureCertPathForEndpoint1());
+        addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, appKeyAlias);
+
+        // Need to check array value during method call since it is modified later.
+        setExpectedScryptArgument(password.getBytes());
+
+        Arrays.fill(zeroizedCredential, (byte) 0);
+        mKeySyncTask.run();
+
+        verify(mMockScrypt).scrypt(any(), any(),
+                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+    }
+
+    @Test
     public void run_useSha256ToHashPatternInProdMode() throws Exception {
         String pattern = "123456";
         mKeySyncTask = new KeySyncTask(
@@ -368,13 +409,15 @@
         mRecoverableKeyStoreDb.setRecoveryServiceCertPath(
                 TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_ROOT_CERT_ALIAS, TestData.CERT_PATH_1);
 
+        setExpectedScryptArgument(shortPassword.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(shortPassword.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
         KeyDerivationParams keyDerivationParams =
@@ -650,13 +693,15 @@
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
+        setExpectedScryptArgument(password.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams()).hasSize(1);
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PASSWORD);
-        verify(mMockScrypt).scrypt(eq(password.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
     }
@@ -681,6 +726,8 @@
         when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
         addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
 
+        setExpectedScryptArgument(pin.getBytes());
+
         mKeySyncTask.run();
 
         KeyChainSnapshot keyChainSnapshot = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
@@ -688,7 +735,7 @@
         // Password with only digits is changed to pin.
         assertThat(keyChainSnapshot.getKeyChainProtectionParams().get(0).getLockScreenUiFormat()).
                 isEqualTo(UI_FORMAT_PIN);
-        verify(mMockScrypt).scrypt(eq(pin.getBytes()), any(),
+        verify(mMockScrypt).scrypt(any(), any(),
                 eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
                 eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
     }
@@ -868,4 +915,14 @@
         new Random().nextBytes(bytes);
         return bytes;
     }
+
+    private void setExpectedScryptArgument(byte[] credentials) {
+        doAnswer(invocation -> {
+            assertThat((byte[]) invocation.getArguments()[0]).isEqualTo(credentials);
+            return invocation.callRealMethod();
+        }).when(mMockScrypt).scrypt(any(), any(),
+                eq(KeySyncTask.SCRYPT_PARAM_N), eq(KeySyncTask.SCRYPT_PARAM_R),
+                eq(KeySyncTask.SCRYPT_PARAM_P), eq(KeySyncTask.SCRYPT_PARAM_OUTLEN_BYTES));
+
+    }
 }
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 bb8b986..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
@@ -62,6 +62,7 @@
 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;
 
@@ -688,6 +689,7 @@
         assertThat(mService.isCurrentProjection(projection)).isTrue();
     }
 
+    @FlakyTest(bugId = 288342281)
     @Test
     public void setContentRecordingSession_successful_notifiesListeners()
             throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 8c07b6c..f834cb2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -52,7 +52,6 @@
             factory.isNamespaceAware = true
             factory.newPullParser()
         }
-        private val ns = "xmlns:android=\"http://schemas.android.com/apk/res/android\""
     }
 
     @Test
@@ -100,19 +99,45 @@
     @Test
     fun parseApplicationTag() {
         val tag = "application"
-        validateTagAttr(tag, "backupAgent",
-            R.styleable.AndroidManifestApplication_backupAgent, 1024)
-        validateTagAttr(tag, "manageSpaceActivity",
-            R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024)
+        validateTagAttr(
+            tag,
+            "backupAgent",
+            R.styleable.AndroidManifestApplication_backupAgent,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "backupAgent",
+            R.styleable.AndroidManifestApplication_backupAgent
+        )
+        validateTagAttr(
+            tag,
+            "manageSpaceActivity",
+            R.styleable.AndroidManifestApplication_manageSpaceActivity,
+            1024
+        )
         validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestApplication_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024)
-        validateTagAttr(tag, "requiredAccountType",
-            R.styleable.AndroidManifestApplication_requiredAccountType, 1024)
-        validateTagAttr(tag, "restrictedAccountType",
-            R.styleable.AndroidManifestApplication_restrictedAccountType, 1024)
-        validateTagAttr(tag, "taskAffinity",
-            R.styleable.AndroidManifestApplication_taskAffinity, 1024)
+        validateTagAttr(
+            tag,
+            "requiredAccountType",
+            R.styleable.AndroidManifestApplication_requiredAccountType,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "restrictedAccountType",
+            R.styleable.AndroidManifestApplication_restrictedAccountType,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "taskAffinity",
+            R.styleable.AndroidManifestApplication_taskAffinity,
+            1024
+        )
         validateTagCount("profileable", 100, tag)
         validateTagCount("uses-native-library", 100, tag)
         validateTagCount("receiver", 1000, tag)
@@ -134,6 +159,7 @@
     fun parseReceiverTag() {
         val tag = "receiver"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestReceiver_name, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestReceiver_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestReceiver_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestReceiver_process, 1024)
         validateTagCount("meta-data", 1000, tag)
@@ -144,6 +170,7 @@
     fun parseServiceTag() {
         val tag = "service"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestService_name, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestService_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestService_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestService_process, 1024)
         validateTagCount("meta-data", 1000, tag)
@@ -154,10 +181,23 @@
     fun parseActivityAliasTag() {
         val tag = "activity-alias"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024)
-        validateTagAttr(tag, "permission",
-            R.styleable.AndroidManifestActivityAlias_permission, 1024)
-        validateTagAttr(tag, "targetActivity",
-            R.styleable.AndroidManifestActivityAlias_targetActivity, 1024)
+        validateTagAttr(
+            tag,
+            "permission",
+            R.styleable.AndroidManifestActivityAlias_permission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "targetActivity",
+            R.styleable.AndroidManifestActivityAlias_targetActivity,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "targetActivity",
+            R.styleable.AndroidManifestActivityAlias_targetActivity
+        )
         validateTagCount("meta-data", 1000, tag)
         validateTagCount("intent-filter", 20000, tag)
     }
@@ -172,8 +212,18 @@
     fun parseActivityTag() {
         val tag = "activity"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024)
-        validateTagAttr(tag, "parentActivityName",
-            R.styleable.AndroidManifestActivity_parentActivityName, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestActivity_name)
+        validateTagAttr(
+            tag,
+            "parentActivityName",
+            R.styleable.AndroidManifestActivity_parentActivityName,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "parentActivityName",
+            R.styleable.AndroidManifestActivity_parentActivityName
+        )
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024)
         validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024)
@@ -186,24 +236,49 @@
     fun parseOverlayTag() {
         val tag = "overlay"
         validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024)
-        validateTagAttr(tag, "requiredSystemPropertyName",
-            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024)
-        validateTagAttr(tag, "requiredSystemPropertyValue",
-            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX)
-        validateTagAttr(tag, "targetPackage",
-            R.styleable.AndroidManifestResourceOverlay_targetPackage, 256)
-        validateTagAttr(tag, "targetName",
-            R.styleable.AndroidManifestResourceOverlay_targetName, 1024)
+        validateTagAttr(
+            tag,
+            "requiredSystemPropertyName",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "requiredSystemPropertyValue",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue,
+            PROP_VALUE_MAX
+        )
+        validateTagAttr(
+            tag,
+            "targetPackage",
+            R.styleable.AndroidManifestResourceOverlay_targetPackage,
+            256
+        )
+        validateTagAttr(
+            tag,
+            "targetName",
+            R.styleable.AndroidManifestResourceOverlay_targetName,
+            1024
+        )
     }
 
     @Test
     fun parseInstrumentationTag() {
         val tag = "instrumentation"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024)
-        validateTagAttr(tag, "targetPackage",
-            R.styleable.AndroidManifestInstrumentation_targetPackage, 256)
-        validateTagAttr(tag, "targetProcesses",
-            R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestInstrumentation_name)
+        validateTagAttr(
+            tag,
+            "targetPackage",
+            R.styleable.AndroidManifestInstrumentation_targetPackage,
+            256
+        )
+        validateTagAttr(
+            tag,
+            "targetProcesses",
+            R.styleable.AndroidManifestInstrumentation_targetProcesses,
+            1024
+        )
     }
 
     @Test
@@ -262,12 +337,21 @@
     fun parseProviderTag() {
         val tag = "provider"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestProvider_name, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestProvider_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024)
-        validateTagAttr(tag, "readPermission",
-            R.styleable.AndroidManifestProvider_readPermission, 1024)
-        validateTagAttr(tag, "writePermission",
-            R.styleable.AndroidManifestProvider_writePermission, 1024)
+        validateTagAttr(
+            tag,
+            "readPermission",
+            R.styleable.AndroidManifestProvider_readPermission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "writePermission",
+            R.styleable.AndroidManifestProvider_writePermission,
+            1024
+        )
         validateTagCount("grant-uri-permission", 100, tag)
         validateTagCount("path-permission", 100, tag)
         validateTagCount("meta-data", 1000, tag)
@@ -278,26 +362,54 @@
     fun parseGrantUriPermissionTag() {
         val tag = "grant-uri-permission"
         validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000)
-        validateTagAttr(tag, "pathPrefix",
-            R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000)
-        validateTagAttr(tag, "pathPattern",
-            R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000)
+        validateTagAttr(
+            tag,
+            "pathPrefix",
+            R.styleable.AndroidManifestGrantUriPermission_pathPrefix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "pathPattern",
+            R.styleable.AndroidManifestGrantUriPermission_pathPattern,
+            4000
+        )
     }
 
     @Test
     fun parsePathPermissionTag() {
         val tag = "path-permission"
         validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000)
-        validateTagAttr(tag, "pathPrefix",
-            R.styleable.AndroidManifestPathPermission_pathPrefix, 4000)
-        validateTagAttr(tag, "pathPattern",
-            R.styleable.AndroidManifestPathPermission_pathPattern, 4000)
-        validateTagAttr(tag, "permission",
-            R.styleable.AndroidManifestPathPermission_permission, 1024)
-        validateTagAttr(tag, "readPermission",
-            R.styleable.AndroidManifestPathPermission_readPermission, 1024)
-        validateTagAttr(tag, "writePermission",
-            R.styleable.AndroidManifestPathPermission_writePermission, 1024)
+        validateTagAttr(
+            tag,
+            "pathPrefix",
+            R.styleable.AndroidManifestPathPermission_pathPrefix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "pathPattern",
+            R.styleable.AndroidManifestPathPermission_pathPattern,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "permission",
+            R.styleable.AndroidManifestPathPermission_permission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "readPermission",
+            R.styleable.AndroidManifestPathPermission_readPermission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "writePermission",
+            R.styleable.AndroidManifestPathPermission_writePermission,
+            1024
+        )
     }
 
     @Test
@@ -336,8 +448,12 @@
         validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000)
         validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000)
         validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000)
-        validateTagAttr(tag, "pathAdvancedPattern",
-            R.styleable.AndroidManifestData_pathAdvancedPattern, 4000)
+        validateTagAttr(
+            tag,
+            "pathAdvancedPattern",
+            R.styleable.AndroidManifestData_pathAdvancedPattern,
+            4000
+        )
         validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
     }
 
@@ -351,8 +467,12 @@
     fun parsePermissionTag() {
         val tag = "permission"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024)
-        validateTagAttr(tag, "permissionGroup",
-            R.styleable.AndroidManifestPermission_permissionGroup, 256)
+        validateTagAttr(
+            tag,
+            "permissionGroup",
+            R.styleable.AndroidManifestPermission_permissionGroup,
+            256
+        )
     }
 
     @Test
@@ -361,6 +481,56 @@
         validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesPermission_name, 1024)
     }
 
+    private fun validateTagAttrComponentName(tag: String, attr: String, index: Int) {
+        val passNames = arrayOf("com.android.TestClass", "TestClass", "_", "$", ".TestClass", "上")
+        for (name in passNames) {
+            val xml = "<$tag $attr=\"$name\" />"
+            pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+            val validator = Validator()
+            pullParser.nextTag()
+            validator.validate(pullParser)
+            try {
+                validator.validateStrAttr(pullParser, attr, name)
+            } catch (e: SecurityException) {
+                fail(
+                    "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+                        " ${e.message}"
+                )
+            }
+            try {
+                validator.validateResStrAttr(pullParser, index, name)
+            } catch (e: SecurityException) {
+                fail(
+                    "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+                        " ${e.message}"
+                )
+            }
+        }
+
+        val failNames = arrayOf("com.android.TestClass:", "-TestClass", "TestClass.", ".", "..")
+        for (name in failNames) {
+            val xml = "<$tag $attr=\"$name\" />"
+            pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
+            val validator = Validator()
+            pullParser.nextTag()
+            validator.validate(pullParser)
+            val e1 = assertThrows(
+                "$name is not valid Java class name",
+                SecurityException::class.java
+            ) {
+                validator.validateStrAttr(pullParser, attr, name)
+            }
+            assertEquals(expectedAttrComponentNameErrorMsg(name), e1.message)
+            val e2 = assertThrows(
+                "$name is not valid Java class name",
+                SecurityException::class.java
+            ) {
+                validator.validateResStrAttr(pullParser, index, name)
+            }
+            assertEquals(expectedAttrComponentNameErrorMsg(name), e2.message)
+        }
+    }
+
     private fun validateTagAttr(tag: String, name: String, index: Int?, maxLen: Int) {
         validateTagAttr_shouldPass(tag, name, index, maxLen)
         validateTagAttr_shouldFail(tag, name, index, maxLen)
@@ -381,15 +551,19 @@
         try {
             validator.validateStrAttr(pullParser, name, value)
         } catch (e: SecurityException) {
-            fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
-                    " ${e.message}")
+            fail(
+                "Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
+                    " ${e.message}"
+            )
         }
         if (index != null) {
             try {
                 validator.validateResStrAttr(pullParser, index, value)
             } catch (e: SecurityException) {
-                fail("Failed to parse valid <$tag> resource string attribute $name with max" +
-                        " length of $maxLen: ${e.message}")
+                fail(
+                    "Failed to parse valid <$tag> resource string attribute $name with max" +
+                        " length of $maxLen: ${e.message}"
+                )
             }
         }
     }
@@ -429,8 +603,10 @@
         try {
             parseXmlStr(xml)
         } catch (e: SecurityException) {
-            fail("Failed to parse <$tag> with max count limit of $maxNum under" +
-                    " <$parentTag>: ${e.message}")
+            fail(
+                "Failed to parse <$tag> with max count limit of $maxNum under" +
+                        " <$parentTag>: ${e.message}"
+            )
         }
     }
 
@@ -468,4 +644,6 @@
 
     fun expectedResAttrLengthErrorMsg(tag: String) =
             "String length limit exceeded for attribute in $tag"
+
+    fun expectedAttrComponentNameErrorMsg(name: String) = "$name is not a valid Java class name"
 }
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
index 22b72fa..f0a7e47 100644
--- a/services/tests/vibrator/TEST_MAPPING
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -1,7 +1,21 @@
 {
-  "imports": [
+  "presubmit": [
     {
-      "path": "frameworks/base/services/core/java/com/android/server/vibrator"
+      "name": "FrameworksVibratorServicesTests",
+      "options": [
+        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "FrameworksVibratorServicesTests",
+      "options": [
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
     }
   ]
 }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
index 8bb21b3..867c061 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/RampToStepAdapterTest.java
@@ -66,6 +66,9 @@
         assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
         assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
 
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1));
+
         assertEquals(originalSegments, segments);
     }
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.java
new file mode 100644
index 0000000..6630cca
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/SplitSegmentsAdapterTest.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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+public class SplitSegmentsAdapterTest {
+    private static final int PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX = 10;
+
+    private static final float[] TEST_AMPLITUDE_MAP = new float[]{
+            /* 50Hz= */ 0.1f, 0.2f, 0.4f, 0.8f, /* 150Hz= */ 1f, 0.9f, /* 200Hz= */ 0.8f};
+
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(
+                    /* resonantFrequencyHz= */ 150f, /* minFrequencyHz= */ 50f,
+                    /* frequencyResolutionHz= */ 25f, TEST_AMPLITUDE_MAP);
+
+    private static final VibratorInfo EMPTY_VIBRATOR_INFO = createVibratorInfo();
+    private static final VibratorInfo PWLE_VIBRATOR_INFO = createVibratorInfo(
+            IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+
+    private SplitSegmentsAdapter mAdapter;
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new SplitSegmentsAdapter();
+    }
+
+    @Test
+    public void testStepAndPrebakedAndPrimitiveSegments_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                new PrebakedSegment(
+                        VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
+                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(originalSegments);
+    }
+
+    @Test
+    public void testRampSegments_noPwleCapabilities_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.2f, /* endAmplitude*/ 0.8f,
+                        /* startFrequencyHz= */ 60, /* endFrequencyHz= */ 90, /* duration= */ 10)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(originalSegments);
+    }
+
+    @Test
+    public void testRampSegments_withPwleDurationLimit_splitsLongRampsAndPreserveOtherSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
+                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
+                new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
+                        /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
+                        /* duration= */ 8),
+                new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
+                        /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
+                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
+
+        VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
+                .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
+                .setPwlePrimitiveDurationMax(10)
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+                .build();
+
+        // Update repeat index to skip the ramp splits.
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 3))
+                .isEqualTo(5);
+        assertThat(segments).isEqualTo(expectedSegments);
+    }
+
+    private static VibratorInfo createVibratorInfo(int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
+                .setPwlePrimitiveDurationMax(PWLE_COMPOSITION_PRIMITIVE_DURATION_MAX)
+                .build();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
index 58deeec..82deff0 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/StepToRampAdapterTest.java
@@ -66,43 +66,13 @@
         assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
         assertEquals(1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, 1));
 
+        assertEquals(-1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(PWLE_VIBRATOR_INFO, segments, 1));
+
         assertEquals(originalSegments, segments);
     }
 
     @Test
-    public void testRampSegments_withPwleDurationLimit_splitsLongRamps() {
-        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 0, /* endFrequencyHz= */ 50, /* duration= */ 25),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5)));
-        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
-                new RampSegment(/* startAmplitude= */ 0.5f, /* endAmplitude*/ 0.5f,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 10, /* duration= */ 10),
-                new RampSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 0.32f,
-                        /* startFrequencyHz= */ 150, /* endFrequencyHz= */ 118f, /* duration= */ 8),
-                new RampSegment(/* startAmplitude= */ 0.32f, /* endAmplitude= */ 0.64f,
-                        /* startFrequencyHz= */ 118f, /* endFrequencyHz= */ 86f,
-                        /* duration= */ 8),
-                new RampSegment(/* startAmplitude= */ 0.64f, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 86f, /* endFrequencyHz= */ 50f, /* duration= */ 9),
-                new RampSegment(/* startAmplitude= */ 1, /* endAmplitude*/ 1,
-                        /* startFrequencyHz= */ 10, /* endFrequencyHz= */ 20, /* duration= */ 5));
-
-        VibratorInfo vibratorInfo = new VibratorInfo.Builder(0)
-                .setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS)
-                .setPwlePrimitiveDurationMax(10)
-                .setFrequencyProfile(TEST_FREQUENCY_PROFILE)
-                .build();
-
-        // Update repeat index to skip the ramp splits.
-        assertEquals(4, mAdapter.adaptToVibrator(vibratorInfo, segments, 2));
-        assertEquals(expectedSegments, segments);
-    }
-
-    @Test
     public void testStepAndRampSegments_withoutPwleCapability_keepsListUnchanged() {
         List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
                 new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 709e9c3..edc5df2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -60,6 +60,7 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
 import android.util.SparseArray;
 
@@ -1004,6 +1005,7 @@
                 mVibratorProviders.get(3).getEffectSegments(vibrationId));
     }
 
+    @FlakyTest
     @Test
     public void vibrate_multipleSyncedCallbackTriggered_finishSteps() throws Exception {
         int[] vibratorIds = new int[]{1, 2};
@@ -1020,9 +1022,12 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
                 .compose();
         CombinedVibration effect = CombinedVibration.createParallel(composed);
-        long vibrationId = startThreadAndDispatcher(effect);
-
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
         when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+        startThreadAndDispatcher(halVibration);
 
         assertTrue(waitUntil(
                 () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
@@ -1054,7 +1059,6 @@
         mVibratorProviders.get(4).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
 
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
@@ -1065,7 +1069,12 @@
                 .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
                 .addVibrator(4, composed)
                 .combine();
-        long vibrationId = startThreadAndDispatcher(effect);
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+        startThreadAndDispatcher(halVibration);
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
@@ -1115,13 +1124,17 @@
         mockVibrators(vibratorIds);
         mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
 
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .combine();
-        long vibrationId = startThreadAndDispatcher(effect);
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false);
+        startThreadAndDispatcher(halVibration);
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 9067593..42e3383 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -65,6 +65,7 @@
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityInVirtualDisplay"
                   android:resizeableActivity="true" />
         <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$LandscapeActivity"
+                  android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation"
                   android:screenOrientation="sensorLandscape"
                   android:showWhenLocked="true"
                   android:turnScreenOn="true" />
diff --git a/services/tests/wmtests/OWNERS b/services/tests/wmtests/OWNERS
index cece37f..78b867f 100644
--- a/services/tests/wmtests/OWNERS
+++ b/services/tests/wmtests/OWNERS
@@ -3,3 +3,5 @@
 
 # Voice Interaction
 per-file *Assist* = file:/core/java/android/service/voice/OWNERS
+
+natanieljr@google.com
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
index 62875e5..896edff 100644
--- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java
@@ -27,6 +27,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -41,6 +42,11 @@
 public class CombinationKeyTests extends ShortcutKeyTestBase {
     private static final long A11Y_KEY_HOLD_MILLIS = 3500;
 
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager();
+    }
+
     /**
      * Power-VolDown to take screenshot.
      */
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 9029bc4..2c35cf0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -44,6 +44,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Before;
 import org.junit.Test;
 
 @Presubmit
@@ -61,6 +62,11 @@
         META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
     }
 
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager();
+    }
+
     /**
      * Test meta+ shortcuts defined in bookmarks.xml.
      */
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 c3b7849..6f65406 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -26,6 +26,7 @@
 import android.provider.Settings;
 import android.view.Display;
 
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -35,6 +36,11 @@
  *  atest WmTests:PowerKeyGestureTests
  */
 public class PowerKeyGestureTests extends ShortcutKeyTestBase {
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager();
+    }
+
     /**
      * Power single press to turn screen on/off.
      */
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 bf88ce4..f83aecb 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.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
 
@@ -53,12 +54,17 @@
 import android.view.KeyEvent;
 import android.view.ViewConfiguration;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+
 import org.junit.After;
-import org.junit.Before;
+import org.junit.Rule;
 
 import java.util.Map;
 
 class ShortcutKeyTestBase {
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
     TestPhoneWindowManager mPhoneWindowManager;
     final Context mContext = spy(getInstrumentation().getTargetContext());
 
@@ -78,18 +84,35 @@
         MODIFIER = unmodifiableMap(map);
     }
 
-    @Before
-    public void setUp() {
+    /** Same as {@link setUpPhoneWindowManager(boolean)}, without supporting settings update. */
+    protected final void setUpPhoneWindowManager() {
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ false);
+    }
+
+    /**
+     * Creates and sets up a {@link TestPhoneWindowManager} instance.
+     *
+     * <p>Subclasses must call this at the start of the test if they intend to interact with phone
+     * window manager.
+     *
+     * @param supportSettingsUpdate {@code true} if this test should read and listen to provider
+     *      settings values.
+     */
+    protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
 
-        mPhoneWindowManager = new TestPhoneWindowManager(mContext);
+        doReturn(mSettingsProviderRule.mockContentResolver(mContext))
+                .when(mContext).getContentResolver();
+        mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
     }
 
     @After
     public void tearDown() {
-        mPhoneWindowManager.tearDown();
+        if (mPhoneWindowManager != null) {
+            mPhoneWindowManager.tearDown();
+        }
     }
 
     void sendKeyCombination(int[] keyCodes, long duration) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index feca326..d6c821f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -19,7 +19,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.KeyEvent;
 
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
 
 import com.android.internal.annotations.Keep;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardLogEvent;
@@ -32,7 +33,7 @@
 import junitparams.Parameters;
 
 @Presubmit
-@SmallTest
+@LargeTest
 @RunWith(JUnitParamsRunner.class)
 public class ShortcutLoggingTests extends ShortcutKeyTestBase {
 
@@ -221,9 +222,8 @@
     }
 
     @Before
-    @Override
     public void setUp() {
-        super.setUp();
+        setUpPhoneWindowManager();
         mPhoneWindowManager.overrideKeyEventSource(VENDOR_ID, PRODUCT_ID);
         mPhoneWindowManager.overrideLaunchHome();
         mPhoneWindowManager.overrideSearchKeyBehavior(
@@ -235,6 +235,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 293273386)
     @Parameters(method = "shortcutTestArguments")
     public void testShortcuts(String testName, int[] testKeys, KeyboardLogEvent expectedLogEvent,
             int expectedKey, int expectedModifierState) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index fe8017e..c433e64 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -16,21 +16,14 @@
 
 package com.android.server.policy;
 
+import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS;
 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 android.provider.Settings;
 
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.Mockito;
 
 /**
  * Test class for stem key gesture.
@@ -39,17 +32,13 @@
  * 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));
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(false);
 
@@ -63,10 +52,9 @@
      */
     @Test
     public void stemSingleKey_AfterSetup_openAllApp() {
-        stemKeySetup(
-                () -> overrideBehavior(
-                        com.android.internal.R.integer.config_shortPressOnStemPrimaryBehavior,
-                        SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS));
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideStartActivity();
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
 
@@ -75,28 +63,7 @@
         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));
+    private void overrideBehavior(String key, int expectedBehavior) {
+        Settings.Global.putLong(mContext.getContentResolver(), key, expectedBehavior);
     }
 }
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 1866767..7e7a9e1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -27,6 +27,7 @@
 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.doAnswer;
 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;
@@ -177,16 +178,16 @@
         }
     }
 
-    TestPhoneWindowManager(Context context) {
+    TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
         MockitoAnnotations.initMocks(this);
         mHandlerThread = new HandlerThread("fake window manager");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
         mContext = mockingDetails(context).isSpy() ? context : spy(context);
-        mHandler.runWithScissors(this::setUp,  0 /* timeout */);
+        mHandler.runWithScissors(() -> setUp(supportSettingsUpdate),  0 /* timeout */);
     }
 
-    private void setUp() {
+    private void setUp(boolean supportSettingsUpdate) {
         mPhoneWindowManager = spy(new PhoneWindowManager());
 
         // Use stubOnly() to reduce memory usage if it doesn't need verification.
@@ -266,7 +267,15 @@
         });
 
         doNothing().when(mPhoneWindowManager).initializeHdmiState();
-        doNothing().when(mPhoneWindowManager).updateSettings();
+        if (supportSettingsUpdate) {
+            doAnswer(inv -> {
+                // Make any call to updateSettings run synchronously for tests.
+                mPhoneWindowManager.updateSettings(null);
+                return null;
+            }).when(mPhoneWindowManager).updateSettings(any(Handler.class));
+        } else {
+            doNothing().when(mPhoneWindowManager).updateSettings(any());
+        }
         doNothing().when(mPhoneWindowManager).screenTurningOn(anyInt(), any());
         doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt());
         doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt(), anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
index bd6ac58..71d40de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityLeakTests.java
@@ -34,6 +34,8 @@
 import android.os.strictmode.InstanceCountViolation;
 import android.util.Log;
 
+import com.android.server.wm.utils.CommonUtils;
+
 import org.junit.After;
 import org.junit.Test;
 
@@ -63,6 +65,10 @@
                 activity.finish();
             }
         }
+        if (!mStartedActivityList.isEmpty()) {
+            CommonUtils.waitUntilActivityRemoved(
+                    mStartedActivityList.get(mStartedActivityList.size() - 1));
+        }
         mStartedActivityList.clear();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index f6f3f03..93adddb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -54,6 +54,8 @@
 
 import androidx.test.filters.MediumTest;
 
+import com.android.server.wm.utils.CommonUtils;
+
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -172,6 +174,7 @@
             instrumentation.removeMonitor(monitor);
             if (mainActivity != null) {
                 mainActivity.finish();
+                CommonUtils.waitUntilActivityRemoved(mainActivity);
             }
         }
     }
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/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index bbec091..41c0caae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -453,6 +453,78 @@
                 displayAreaBounds.width(), displayAreaBounds.height());
     }
 
+    @Test
+    public void testDisplayContentUpdatesRecording_withoutSurface() {
+        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+        // mirror.
+        setUpDefaultTaskDisplayAreaWindowToken();
+
+        // WHEN getting the DisplayContent for the new virtual display without providing a valid
+        // size from getDisplaySurfaceDefaultSize, i.e. the case of null surface.
+        final DisplayContent virtualDisplay =
+                mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+        doReturn(null).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(anyInt());
+        mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
+        virtualDisplay.updateRecording();
+
+        // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
+        assertThat(virtualDisplay.isCurrentlyRecording()).isFalse();
+    }
+
+    @Test
+    public void testDisplayContentUpdatesRecording_withSurface() {
+        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+        // mirror.
+        setUpDefaultTaskDisplayAreaWindowToken();
+
+        // WHEN getting the DisplayContent for the virtual display with a valid size from
+        // getDisplaySurfaceDefaultSize (done by surfaceControlMirrors in setUp).
+        final DisplayContent virtualDisplay =
+                mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+        mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm);
+        virtualDisplay.updateRecording();
+
+        // THEN mirroring is initiated for the default display's DisplayArea.
+        assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
+    public void testDisplayContentUpdatesRecording_displayMirroring() {
+        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
+        // mirror.
+        setUpDefaultTaskDisplayAreaWindowToken();
+
+        // GIVEN SurfaceControl can successfully mirror the provided surface.
+        surfaceControlMirrors(sSurfaceSize);
+        // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
+        // call in the test. We need to spy on the DC before updateRecording is called or we can't
+        // verify setDisplayMirroring is called
+        doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
+        // WHEN getting the DisplayContent for the new virtual display.
+        final DisplayContent virtualDisplay =
+                mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId());
+        // Return the default display as the value to mirror to ensure the VD with flag mirroring
+        // creates a ContentRecordingSession automatically.
+        doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+        virtualDisplay.updateRecording();
+
+        // THEN mirroring is initiated for the default display's DisplayArea.
+        verify(virtualDisplay).setDisplayMirroring();
+        assertThat(virtualDisplay.isCurrentlyRecording()).isTrue();
+    }
+
+    /**
+     * Creates a WindowToken associated with the default task DisplayArea, in order for that
+     * DisplayArea to be mirrored.
+     */
+    private void setUpDefaultTaskDisplayAreaWindowToken() {
+        // GIVEN the default task display area is represented by the WindowToken.
+        spyOn(mWm.mWindowContextListenerController);
+        doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
+                mWm.mWindowContextListenerController).getContainer(any());
+    }
+
     /**
      * Creates a {@link android.window.WindowContainerToken} associated with a task, in order for
      * that task to be recorded.
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 bdd178b..d015ca3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -27,12 +27,10 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_PRIVATE;
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
 import static android.view.Surface.ROTATION_0;
@@ -112,11 +110,9 @@
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Insets;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
-import android.hardware.display.VirtualDisplay;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -124,7 +120,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
-import android.view.ContentRecordingSession;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -2655,138 +2650,6 @@
     }
 
     @Test
-    public void testVirtualDisplayContent_withoutSurface() {
-        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
-        // mirror.
-        setUpDefaultTaskDisplayAreaWindowToken();
-
-        // GIVEN SurfaceControl does not mirror a null surface.
-        Point surfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-
-        // GIVEN a new VirtualDisplay with an associated surface.
-        final VirtualDisplay display = createVirtualDisplay(surfaceSize, null /* surface */);
-        final int displayId = display.getDisplay().getDisplayId();
-        mWm.mRoot.onDisplayAdded(displayId);
-
-        // WHEN getting the DisplayContent for the new virtual display.
-        DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
-        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
-                DEFAULT_DISPLAY);
-        session.setVirtualDisplayId(displayId);
-        mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
-        actualDC.updateRecording();
-
-        // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off.
-        assertThat(actualDC.isCurrentlyRecording()).isFalse();
-
-        display.release();
-    }
-
-    @Test
-    public void testVirtualDisplayContent_withSurface() {
-        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
-        // mirror.
-        setUpDefaultTaskDisplayAreaWindowToken();
-
-        // GIVEN SurfaceControl can successfully mirror the provided surface.
-        Point surfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        surfaceControlMirrors(surfaceSize);
-
-        // GIVEN a new VirtualDisplay with an associated surface.
-        final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
-        final int displayId = display.getDisplay().getDisplayId();
-
-        // GIVEN a session for this display.
-        ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
-                DEFAULT_DISPLAY);
-        session.setVirtualDisplayId(displayId);
-        mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm);
-        mWm.mRoot.onDisplayAdded(displayId);
-
-        // WHEN getting the DisplayContent for the new virtual display.
-        DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
-        actualDC.updateRecording();
-
-        // THEN mirroring is initiated for the default display's DisplayArea.
-        assertThat(actualDC.isCurrentlyRecording()).isTrue();
-
-        display.release();
-    }
-
-    @Test
-    public void testVirtualDisplayContent_displayMirroring() {
-        // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
-        // mirror.
-        setUpDefaultTaskDisplayAreaWindowToken();
-
-        // GIVEN SurfaceControl can successfully mirror the provided surface.
-        Point surfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        surfaceControlMirrors(surfaceSize);
-        // Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
-        // call in the test. We need to spy on the DC before updateRecording is called or we can't
-        // verify setDisplayMirroring is called
-        doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
-
-        // GIVEN a new VirtualDisplay with an associated surface.
-        final VirtualDisplay display = createVirtualDisplay(surfaceSize, new Surface());
-        final int displayId = display.getDisplay().getDisplayId();
-
-        // GIVEN a session for this display.
-        mWm.mRoot.onDisplayAdded(displayId);
-
-        // WHEN getting the DisplayContent for the new virtual display.
-        DisplayContent actualDC = mWm.mRoot.getDisplayContent(displayId);
-        spyOn(actualDC);
-        // Return the default display as the value to mirror to ensure the VD with flag mirroring
-        // creates a ContentRecordingSession automatically.
-        doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
-        actualDC.updateRecording();
-
-        // THEN mirroring is initiated for the default display's DisplayArea.
-        verify(actualDC).setDisplayMirroring();
-        assertThat(actualDC.isCurrentlyRecording()).isTrue();
-        display.release();
-    }
-
-    /**
-     * Creates a WindowToken associated with the default task DisplayArea, in order for that
-     * DisplayArea to be mirrored.
-     */
-    private void setUpDefaultTaskDisplayAreaWindowToken() {
-        // GIVEN the default task display area is represented by the WindowToken.
-        spyOn(mWm.mWindowContextListenerController);
-        doReturn(mDefaultDisplay.getDefaultTaskDisplayArea()).when(
-                mWm.mWindowContextListenerController).getContainer(any());
-    }
-
-    /**
-     * SurfaceControl successfully creates a mirrored surface of the given size.
-     */
-    private SurfaceControl surfaceControlMirrors(Point surfaceSize) {
-        // Do not set the parent, since the mirrored surface is the root of a new surface hierarchy.
-        SurfaceControl mirroredSurface = new SurfaceControl.Builder()
-                .setName("mirroredSurface")
-                .setBufferSize(surfaceSize.x, surfaceSize.y)
-                .setCallsite("mirrorSurface")
-                .build();
-        doReturn(mirroredSurface).when(() -> SurfaceControl.mirrorSurface(any()));
-        doReturn(surfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
-                anyInt());
-        return mirroredSurface;
-    }
-
-    private VirtualDisplay createVirtualDisplay(Point size, Surface surface) {
-        return mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", size.x, size.y,
-                DisplayMetrics.DENSITY_140, surface, VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR);
-    }
-
-    @Test
     public void testKeepClearAreasMultipleWindows() {
         final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1");
         final Rect rect1 = new Rect(0, 0, 10, 10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
index 1180ebd..c3db241 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodDialogWindowContextTest.java
@@ -47,6 +47,7 @@
 import android.view.IWindowManager;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
+import android.window.WindowContextInfo;
 import android.window.WindowTokenClient;
 
 import com.android.server.inputmethod.InputMethodDialogWindowContext;
@@ -99,7 +100,7 @@
             final WindowProcessController wpc = mAtm.getProcessController(appThread);
             mWm.mWindowContextListenerController.registerWindowContainerListener(wpc, clientToken,
                     dc.getImeContainer(), TYPE_INPUT_METHOD_DIALOG, null /* options */);
-            return dc.getImeContainer().getConfiguration();
+            return new WindowContextInfo(dc.getImeContainer().getConfiguration(), displayId);
         }).when(mIWindowManager).attachWindowContextToDisplayArea(any(), any(),
                 eq(TYPE_INPUT_METHOD_DIALOG), anyInt(), any());
         mDisplayManagerGlobal = DisplayManagerGlobal.getInstance();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index fb95748..0fcae92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -56,6 +56,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -90,6 +93,11 @@
         mInstrumentation.waitForIdleSync();
     }
 
+    @After
+    public void tearDown() {
+        CommonUtils.waitUntilActivityRemoved(mActivity);
+    }
+
     @Test
     public void testScreenshotSecureLayers() throws InterruptedException {
         SurfaceControl secureSC = new SurfaceControl.Builder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
index 1a4b94b..ce1a46b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -52,6 +52,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.server.wm.utils.CommonUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -87,6 +89,7 @@
     @After
     public void tearDown() {
         mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        CommonUtils.waitUntilActivityRemoved(mActivity);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
index abaa776..77290e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTests.java
@@ -47,6 +47,9 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -79,6 +82,12 @@
         mHandler = mHandlerThread.getThreadHandler();
     }
 
+    @After
+    public void tearDown() {
+        mHandlerThread.quitSafely();
+        CommonUtils.waitUntilActivityRemoved(mActivity);
+    }
+
     @Test
     public void testOverlappingSyncsEnsureOrder_WhenTimeout() throws InterruptedException {
         WindowManager.LayoutParams params = new WindowManager.LayoutParams();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
index f958e6f..c59a04b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceViewSyncContinuousTest.java
@@ -25,6 +25,9 @@
 
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.server.wm.utils.CommonUtils;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -54,6 +57,11 @@
         }
     }
 
+    @After
+    public void tearDown() {
+        CommonUtils.waitUntilActivityRemoved(mCapturedActivity);
+    }
+
     @Test
     public void testSurfaceViewSyncDuringResize() throws Throwable {
         mCapturedActivity.verifyTest(new SurfaceViewSyncValidatorTestCase(), mName);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 8bc4ced..db08eab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -34,6 +34,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.InputChannel;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -72,6 +73,7 @@
         doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
     }
 
+    @FlakyTest(bugId = 291067614)
     @Test
     public void testStartAndFinishPositioning() {
         assertFalse(mTarget.isPositioningLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index e16208b..16c38ac 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -60,6 +60,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.CountDownLatch;
@@ -76,6 +77,7 @@
     private ITaskStackListener mTaskStackListener;
     private VirtualDisplay mVirtualDisplay;
     private ImageReader mImageReader;
+    private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
 
     private static final int WAIT_TIMEOUT_MS = 5000;
     private static final Object sLock = new Object();
@@ -94,6 +96,19 @@
             mVirtualDisplay.release();
             mImageReader.close();
         }
+        // Finish from bottom to top.
+        final int size = mStartedActivities.size();
+        for (int i = 0; i < size; i++) {
+            final Activity activity = mStartedActivities.get(i);
+            if (!activity.isFinishing()) {
+                activity.finish();
+            }
+        }
+        // Wait for the last launched activity to be removed.
+        if (size > 0) {
+            CommonUtils.waitUntilActivityRemoved(mStartedActivities.get(size - 1));
+        }
+        mStartedActivities.clear();
     }
 
     private VirtualDisplay createVirtualDisplay() {
@@ -381,6 +396,7 @@
             throw new RuntimeException("Timed out waiting for Activity");
         }
         activity.waitForResumeStateChange(true);
+        mStartedActivities.add(activity);
         return activity;
     }
 
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 d502cd1..55fda05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -21,9 +21,6 @@
 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.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_OWN_FOCUS;
 import static android.view.Display.INVALID_DISPLAY;
@@ -63,6 +60,8 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -71,9 +70,7 @@
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
 import android.content.pm.ActivityInfo;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.display.VirtualDisplay;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -82,7 +79,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.view.ContentRecordingSession;
 import android.view.IWindow;
@@ -90,7 +86,6 @@
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
-import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInsets;
@@ -473,7 +468,7 @@
         mWm.attachWindowContextToWindowToken(mAppThread, new Binder(), windowToken.token);
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(
-                any(), any(), any(), anyInt(), any());
+                any(), any(), any(), anyInt(), any(), anyBoolean());
     }
 
     @Test
@@ -489,9 +484,9 @@
         final IBinder clientToken = new Binder();
         mWm.attachWindowContextToWindowToken(mAppThread, clientToken, windowToken.token);
         final WindowProcessController wpc = mAtm.getProcessController(mAppThread);
-        verify(mWm.mWindowContextListenerController).registerWindowContainerListener(eq(wpc),
-                eq(clientToken), eq(windowToken), eq(TYPE_INPUT_METHOD),
-                eq(windowToken.mOptions));
+        verify(mWm.mWindowContextListenerController).registerWindowContainerListener(wpc,
+                clientToken, windowToken, TYPE_INPUT_METHOD, windowToken.mOptions,
+                false /* shouldDispatchConfigWhenRegistering */);
     }
 
     @Test
@@ -519,7 +514,7 @@
                 new InsetsSourceControl.Array(), new Rect(), new float[1]);
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
-                any(), any(), anyInt(), any());
+                any(), any(), anyInt(), any(), anyBoolean());
     }
 
     @Test
@@ -575,13 +570,14 @@
         mWm.mPerDisplayFocusEnabled = false;
 
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
-        final VirtualDisplay virtualDisplayOwnTouchMode =
-                createVirtualDisplay(/* ownFocus= */ true);
+        final DisplayContent display = createMockSimulatedDisplay();
+        display.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
+        final DisplayContent displayOwnTouchMode = createMockSimulatedDisplay();
+        displayOwnTouchMode.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(3);
         final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
-                .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                .filter(d -> (d.getDisplayInfo().flags & FLAG_OWN_FOCUS) == 0)
                 .count();
         assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
@@ -601,12 +597,13 @@
     }
 
     @Test
-    public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
+    public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
         // Enable global touch mode
         mWm.mPerDisplayFocusEnabled = true;
 
         // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+        final DisplayContent virtualDisplay = createMockSimulatedDisplay();
+        virtualDisplay.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
@@ -618,99 +615,64 @@
         when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
                 android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
 
-        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.mDisplayId);
 
         // Ensure that new display touch mode state has changed.
         verify(mWm.mInputManager).setInTouchMode(
                 !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
-                virtualDisplay.getDisplay().getDisplayId());
-    }
+                virtualDisplay.mDisplayId);
 
-    @Test
-    public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
-        // Disable global touch mode
+        // Disable global touch mode and make the virtual display own focus.
         mWm.mPerDisplayFocusEnabled = false;
-
-        // Create one extra display
-        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
-        final int numberOfDisplays = mWm.mRoot.mChildren.size();
-        assertThat(numberOfDisplays).isAtLeast(2);
-
-        // Get current touch mode state and setup WMS to run setInTouchMode
-        boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
-        int callingPid = Binder.getCallingPid();
-        int callingUid = Binder.getCallingUid();
-        doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
-        when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
-                android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
-
-        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+        virtualDisplay.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
+        clearInvocations(mWm.mInputManager);
+        mWm.setInTouchMode(!currentTouchMode, virtualDisplay.mDisplayId);
 
         // Ensure that new display touch mode state has changed.
         verify(mWm.mInputManager).setInTouchMode(
                 !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
-                virtualDisplay.getDisplay().getDisplayId());
+                virtualDisplay.mDisplayId);
     }
 
     @Test
-    public void testSetInTouchModeOnAllDisplays_perDisplayFocusDisabled() {
-        testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ false);
-    }
-
-    @Test
-    public void testSetInTouchModeOnAllDisplays_perDisplayFocusEnabled() {
-        testSetInTouchModeOnAllDisplays(/* perDisplayFocusEnabled= */ true);
-    }
-
-    private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
-        // Set global touch mode with the value passed as argument.
-        mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
-
+    public void testSetInTouchModeOnAllDisplays() {
         // Create a couple of extra displays.
         // setInTouchModeOnAllDisplays should ignore the ownFocus setting.
-        final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
-        final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
+        final DisplayContent display = createMockSimulatedDisplay();
+        display.getDisplayInfo().flags &= ~FLAG_OWN_FOCUS;
+        final DisplayContent displayOwnTouchMode = createMockSimulatedDisplay();
+        displayOwnTouchMode.getDisplayInfo().flags |= FLAG_OWN_FOCUS;
 
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
+        doReturn(true).when(mWm.mInputManager).setInTouchMode(anyBoolean(), anyInt(),
+                anyInt(), anyBoolean(), anyInt());
         doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
         when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
                 android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
 
-        for (boolean inTouchMode : new boolean[]{true, false}) {
-            mWm.setInTouchModeOnAllDisplays(inTouchMode);
-            for (int i = 0; i < mWm.mRoot.mChildren.size(); ++i) {
-                DisplayContent dc = mWm.mRoot.mChildren.get(i);
-                // All displays that are not already in the desired touch mode are requested to
-                // change their touch mode.
-                if (dc.isInTouchMode() != inTouchMode) {
-                    verify(mWm.mInputManager).setInTouchMode(
-                            true, callingPid, callingUid, /* hasPermission= */ true,
-                            dc.getDisplay().getDisplayId());
+        final Runnable verification = () -> {
+            for (boolean inTouchMode : new boolean[] { true, false }) {
+                mWm.setInTouchModeOnAllDisplays(inTouchMode);
+                for (int i = 0; i < mRootWindowContainer.getChildCount(); ++i) {
+                    final DisplayContent dc = mRootWindowContainer.getChildAt(i);
+                    // All displays that are not already in the desired touch mode are requested to
+                    // change their touch mode.
+                    if (dc.isInTouchMode() != inTouchMode) {
+                        verify(mWm.mInputManager, description("perDisplayFocusEnabled="
+                                + mWm.mPerDisplayFocusEnabled)).setInTouchMode(true,
+                                callingPid, callingUid, /* hasPermission= */ true, dc.mDisplayId);
+                    }
                 }
             }
-        }
-    }
+        };
 
-    private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
-        // Create virtual display
-        Point surfaceSize = new Point(
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
-                mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
-        int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
-        if (ownFocus) {
-            flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
-        }
-        VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
-                surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
-        final int displayId = virtualDisplay.getDisplay().getDisplayId();
-        mWm.mRoot.onDisplayAdded(displayId);
+        mWm.mPerDisplayFocusEnabled = false;
+        verification.run();
 
-        // Ensure that virtual display was properly created and stored in WRC
-        assertThat(mWm.mRoot.getDisplayContent(
-                virtualDisplay.getDisplay().getDisplayId())).isNotNull();
-
-        return virtualDisplay;
+        clearInvocations(mWm.mInputManager);
+        mWm.mPerDisplayFocusEnabled = true;
+        verification.run();
     }
 
     @Test
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 873c7f4..62de67a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -901,6 +901,7 @@
         DisplayInfo displayInfo = new DisplayInfo();
         displayInfo.copyFrom(mDisplayInfo);
         displayInfo.type = Display.TYPE_VIRTUAL;
+        displayInfo.state = Display.STATE_ON;
         displayInfo.ownerUid = SYSTEM_UID;
         return createNewDisplay(displayInfo, DISPLAY_IME_POLICY_FALLBACK_DISPLAY, overrideSettings);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
index 34f9c75..ed23296 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/CommonUtils.java
@@ -18,14 +18,23 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import android.app.Activity;
 import android.app.KeyguardManager;
 import android.app.UiAutomation;
+import android.os.SystemClock;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.test.uiautomator.UiDevice;
 
+import java.io.IOException;
+
 /** Provides common utility functions. */
 public class CommonUtils {
+    private static final String TAG = "CommonUtils";
+    private static final long REMOVAL_TIMEOUT_MS = 3000;
+    private static final long TIMEOUT_INTERVAL_MS = 200;
+
     public static UiAutomation getUiAutomation() {
         return getInstrumentation().getUiAutomation();
     }
@@ -50,4 +59,26 @@
         device.pressKeyCode(KeyEvent.KEYCODE_WAKEUP);
         device.pressKeyCode(KeyEvent.KEYCODE_MENU);
     }
+
+    public static void waitUntilActivityRemoved(Activity activity) {
+        if (!activity.isFinishing()) {
+            activity.finish();
+        }
+        final UiDevice uiDevice = UiDevice.getInstance(getInstrumentation());
+        final String classPattern = activity.getComponentName().flattenToShortString();
+        final long startTime = SystemClock.uptimeMillis();
+        while (SystemClock.uptimeMillis() - startTime <= REMOVAL_TIMEOUT_MS) {
+            SystemClock.sleep(TIMEOUT_INTERVAL_MS);
+            final String windowTokenDump;
+            try {
+                windowTokenDump = uiDevice.executeShellCommand("dumpsys window tokens");
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            if (!windowTokenDump.contains(classPattern)) {
+                return;
+            }
+        }
+        Log.i(TAG, "Removal timeout of " + classPattern);
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 997015f..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 " +
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index ed1c41f..314150b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9407,10 +9407,11 @@
             "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:
+     * A PersistableBundle that contains a list of key-value pairs, where the values are integer
+     * arrays.
+     * <p>
+     * Keys are the PLMNs of satellite providers as strings and values are integer arrays 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>
@@ -9419,19 +9420,35 @@
      * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
      * </ul>
      * <p>
-     * If this carrier config is not present, the overlay config
+     * An example config for two PLMNs "123411" and "123412":
+     * <pre>{@code
+     * <carrier_config>
+     *   <pbundle_as_map name="carrier_supported_satellite_services_per_provider_bundle">
+     *     <int-array name = "123411" num = "2">
+     *       <item value = "3"/>
+     *       <item value = "5"/>
+     *     </int-array>
+     *     <int-array name = "123412" num = "1">
+     *       <item value = "3"/>
+     *     </int-array>
+     *   </pbundle_as_map>
+     * </carrier_config>
+     * }</pre>
+     * <p>
+     * If this carrier config is not present, the device 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:
+     * is present, the supported services associated with the PLMNs listed in the carrier config
+     * will override that of the device overlay config. 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>
+     * <li>For each PLMN that exists only in the carrier provided satellite services, use the
+     * carrier provided services as the supported services.</li>
+     * <li>For each PLMN that is present only in the device provided satellite services, use the
+     * device provided services as the supported services.</li>
+     * <li>For each PLMN that is present in both the carrier provided and device provided satellite
+     * services, use the carrier provided services as the supported services.</li>
      * </ul>
-     *
+     * <p>
      * This config is empty by default.
      */
     public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE =
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
index 063e2c3..e0fb751 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -93,7 +93,7 @@
      * 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
+    @FlakyTest(bugId = 293581770)
     @Test
     fun navBarWindowsVisibilityChanges() {
         Assume.assumeFalse(flicker.scenario.isTablet)
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index be87766..3d83caf2 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "androidfw/BigBuffer.h"
@@ -229,14 +230,29 @@
   static const char* const sMinorVersion = "19";
 
   // The build id of aapt2 binary.
-  static std::string sBuildId = android::build::GetBuildNumber();
+  static const std::string sBuildId = [] {
+    std::string buildNumber = android::build::GetBuildNumber();
 
-  if (android::base::StartsWith(sBuildId, "eng.")) {
-    time_t now = time(0);
-    tm* ltm = localtime(&now);
+    if (android::base::StartsWith(buildNumber, "eng.")) {
+      // android::build::GetBuildNumber() returns something like "eng.user.20230725.214219" where
+      // the latter two parts are "yyyyMMdd.HHmmss" at build time. Use "yyyyMM" in the fingerprint.
+      std::vector<std::string> parts = util::Split(buildNumber, '.');
+      int buildYear;
+      int buildMonth;
+      if (parts.size() < 3 || parts[2].length() < 6 ||
+          !android::base::ParseInt(parts[2].substr(0, 4), &buildYear) ||
+          !android::base::ParseInt(parts[2].substr(4, 2), &buildMonth)) {
+        // Fallback to localtime() if GetBuildNumber() returns an unexpected output.
+        time_t now = time(0);
+        tm* ltm = localtime(&now);
+        buildYear = 1900 + ltm->tm_year;
+        buildMonth = 1 + ltm->tm_mon;
+      }
 
-    sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon);
-  }
+      buildNumber = android::base::StringPrintf("eng.%04d%02d", buildYear, buildMonth);
+    }
+    return buildNumber;
+  }();
 
   return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str());
 }
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/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();
         }