Merge "Modify usagestats event processing thread" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ab20edc..95b6155 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -39,6 +39,7 @@
     ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
     ":android.app.flags-aconfig-java{.generated_srcjars}",
     ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+    ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
 ]
 
 filegroup {
@@ -369,3 +370,16 @@
     aconfig_declarations: "android.credentials.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Content Protection
+aconfig_declarations {
+    name: "android.view.contentprotection.flags-aconfig",
+    package: "android.view.contentprotection.flags",
+    srcs: ["core/java/android/view/contentprotection/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.view.contentprotection.flags-aconfig-java",
+    aconfig_declarations: "android.view.contentprotection.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/Android.bp b/api/Android.bp
index f017a47..45e70719 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -62,15 +62,6 @@
 metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
 metalava_cmd += " --quiet "
 
-genrule {
-    name: "current-api-xml",
-    tools: ["metalava"],
-    srcs: [":frameworks-base-api-current.txt"],
-    out: ["current.api"],
-    cmd: metalava_cmd + "signature-to-jdiff $(in) $(out)",
-    visibility: ["//visibility:public"],
-}
-
 combined_apis {
     name: "frameworks-base-api",
     bootclasspath: [
diff --git a/api/OWNERS b/api/OWNERS
index bf6216c..965093c 100644
--- a/api/OWNERS
+++ b/api/OWNERS
@@ -3,7 +3,10 @@
 # Modularization team
 file:platform/packages/modules/common:/OWNERS
 
+# Soong plugin owned by Soong team.
+per-file *.go,go.mod,go.work,go.work.sum = file:platform/build/soong:/OWNERS
+
 per-file Android.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 
 # For metalava team to disable lint checks in platform
-per-file Android.bp = aurimas@google.com,emberrose@google.com,sjgilbert@google.com
+per-file Android.bp = aurimas@google.com,emberrose@google.com
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index e5e0ad3..2d9c988 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -359,13 +359,15 @@
     ],
     srcs: [":module-lib-api-stubs-docs-non-updatable"],
     libs: [
+        // We cannot depend on all-modules-module-lib-stubs, because the module-lib stubs
+        // depend on this stub. We resolve dependencies on APIs in modules by depending
+        // on a prebuilt of the whole platform (sdk_system_current_android).
+        // That prebuilt does not include module-lib APIs, so use the prebuilt module-lib
+        // stubs for modules that export module-lib stubs that the non-updatable part
+        // depends on.
         "sdk_module-lib_current_framework-tethering",
         "sdk_module-lib_current_framework-connectivity-t",
-        "sdk_public_current_framework-bluetooth",
-        // NOTE: The below can be removed once the prebuilt stub contains bluetooth.
         "sdk_system_current_android",
-        // NOTE: The below can be removed once the prebuilt stub contains IKE.
-        "sdk_system_current_android.net.ipsec.ike",
     ],
     dist: {
         dir: "apistubs/android/module-lib",
diff --git a/core/api/current.txt b/core/api/current.txt
index 43aaee8..2b50e38 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15224,6 +15224,7 @@
     method public int getMaxAnisotropy();
     method public void setFilterMode(int);
     method public void setMaxAnisotropy(@IntRange(from=1) int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.gainmap_animations") public void setOverrideGainmap(@Nullable android.graphics.Gainmap);
     field public static final int FILTER_MODE_DEFAULT = 0; // 0x0
     field public static final int FILTER_MODE_LINEAR = 2; // 0x2
     field public static final int FILTER_MODE_NEAREST = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e997259..358c8e7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12731,16 +12731,11 @@
 
   public final class HotwordTrainingData implements android.os.Parcelable {
     method public int describeContents();
-    method public static int getMaxTrainingDataSize();
+    method public static int getMaxTrainingDataBytes();
     method public int getTimeoutStage();
-    method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudios();
+    method @NonNull public java.util.List<android.service.voice.HotwordTrainingAudio> getTrainingAudioList();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
-    field public static final int TIMEOUT_STAGE_EARLY = 2; // 0x2
-    field public static final int TIMEOUT_STAGE_LATE = 4; // 0x4
-    field public static final int TIMEOUT_STAGE_MIDDLE = 3; // 0x3
-    field public static final int TIMEOUT_STAGE_UNKNOWN = 0; // 0x0
-    field public static final int TIMEOUT_STAGE_VERY_EARLY = 1; // 0x1
   }
 
   public static final class HotwordTrainingData.Builder {
@@ -12748,7 +12743,7 @@
     method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
     method @NonNull public android.service.voice.HotwordTrainingData build();
     method @NonNull public android.service.voice.HotwordTrainingData.Builder setTimeoutStage(int);
-    method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudios(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>);
+    method @NonNull public android.service.voice.HotwordTrainingData.Builder setTrainingAudioList(@NonNull java.util.List<android.service.voice.HotwordTrainingAudio>);
   }
 
   public interface SandboxedDetectionInitializer {
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index b11a686..4fb7b6b 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -19,8 +19,12 @@
     Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`)
 ListenerLast: android.telephony.satellite.SatelliteManager#stopSatelliteTransmissionUpdates(android.telephony.satellite.SatelliteTransmissionUpdateCallback, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>) parameter #2:
     Listeners should always be at end of argument list (method `stopSatelliteTransmissionUpdates`)
-
-
+MissingGetterMatchingBuilder: android.service.voice.HotwordTrainingData.Builder#addTrainingAudio(android.service.voice.HotwordTrainingAudio):
+    android.service.voice.HotwordTrainingData does not declare a `getTrainingAudios()` method matching method android.service.voice.HotwordTrainingData.Builder.addTrainingAudio(android.service.voice.HotwordTrainingAudio)
+MissingGetterMatchingBuilder: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallViaAudioProcessing(boolean):
+    android.telecom.CallScreeningService.CallResponse does not declare a `shouldScreenCallViaAudioProcessing()` method matching method android.telecom.CallScreeningService.CallResponse.Builder.setShouldScreenCallViaAudioProcessing(boolean)
+MissingGetterMatchingBuilder: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+    android.telephony.mbms.DownloadRequest does not declare a `getServiceId()` method matching method android.telephony.mbms.DownloadRequest.Builder.setServiceId(String)
 MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
     Missing nullability on parameter `intent` in method `onUnbind`
 MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
@@ -327,8 +331,12 @@
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.describeContents()
 UnflaggedApi: android.service.voice.HotwordTrainingData#getMaxTrainingDataSize():
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getMaxTrainingDataSize()
+UnflaggedApi: android.service.voice.HotwordTrainingData#getMaxTrainingDataBytes():
+    New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getMaxTrainingDataBytes()
 UnflaggedApi: android.service.voice.HotwordTrainingData#getTimeoutStage():
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTimeoutStage()
+UnflaggedApi: android.service.voice.HotwordTrainingData#getTrainingAudioList():
+    New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTrainingAudioList()
 UnflaggedApi: android.service.voice.HotwordTrainingData#getTrainingAudios():
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.getTrainingAudios()
 UnflaggedApi: android.service.voice.HotwordTrainingData#writeToParcel(android.os.Parcel, int):
@@ -343,6 +351,8 @@
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.build()
 UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTimeoutStage(int):
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTimeoutStage(int)
+UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTrainingAudioList(java.util.List<android.service.voice.HotwordTrainingAudio>):
+    New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTrainingAudioList(java.util.List<android.service.voice.HotwordTrainingAudio>)
 UnflaggedApi: android.service.voice.HotwordTrainingData.Builder#setTrainingAudios(java.util.List<android.service.voice.HotwordTrainingAudio>):
     New API must be flagged with @FlaggedApi: method android.service.voice.HotwordTrainingData.Builder.setTrainingAudios(java.util.List<android.service.voice.HotwordTrainingAudio>)
 UnflaggedApi: android.telecom.StreamingCall#EXTRA_CALL_ID:
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 951905b..7809048 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -8,5 +8,38 @@
         }
       ]
     }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.timedetector."
+        },
+        {
+          "include-filter": "android.app.timezonedetector."
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timezonedetector."
+        },
+        {
+          "include-filter": "com.android.server.timedetector."
+        }
+      ]
+    },
+    {
+      "name": "CtsTimeTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
new file mode 100644
index 0000000..53fd74b
--- /dev/null
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -0,0 +1,32 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.time."
+        },
+        {
+          "include-filter": "android.app.timedetector."
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timedetector."
+        }
+      ]
+    },
+    {
+      "name": "CtsTimeTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index 46f2319..5e64c83 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -8,5 +8,32 @@
         }
       ]
     }
+  ],
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.time."
+        }
+      ]
+    },
+    {
+      "name": "FrameworksServicesTests",
+      "options": [
+        {
+          "include-filter": "com.android.server.timezonedetector."
+        }
+      ]
+    },
+    {
+      "name": "CtsTimeTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index baed7f9..39800f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -637,15 +637,15 @@
         /**
          * Specifies a component name to be exempt from the current activity launch policy.
          *
-         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
-         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
          * then the specified component will be blocked from launching.
-         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
-         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
-         * the specified component will be allowed to launch.</p>
+         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+         * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+         * specified component will be allowed to launch.</p>
          *
-         * <p>Note that changing the activity launch policy will not affect current set of exempt
-         * components and it needs to be updated separately.</p>
+         * <p>Note that changing the activity launch policy will clear current set of exempt
+         * components.</p>
          *
          * @see #removeActivityPolicyExemption
          * @see #setDevicePolicy
@@ -660,15 +660,15 @@
         /**
          * Makes the specified component name to adhere to the default activity launch policy.
          *
-         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
-         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} allows activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT}),
          * then the specified component will be allowed to launch.
-         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
-         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
-         * the specified component will be blocked from launching.</p>
+         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity launches
+         * by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}), then the
+         * specified component will be blocked from launching.</p>
          *
-         * <p>Note that changing the activity launch policy will not affect current set of exempt
-         * components and it needs to be updated separately.</p>
+         * <p>Note that changing the activity launch policy will clear current set of exempt
+         * components.</p>
          *
          * @see #addActivityPolicyExemption
          * @see #setDevicePolicy
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index a9eb672..1307dfc 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -484,7 +484,6 @@
     /**
      * A callback to be invoked when the system successfully delivers your {@link NdefMessage}
      * to another device.
-     * @see #setOnNdefPushCompleteCallback
      * @deprecated this feature is removed. File sharing can work using other technology like
      * Bluetooth.
      */
@@ -496,7 +495,6 @@
          * <p>This callback is usually made on a binder thread (not the UI thread).
          *
          * @param event {@link NfcEvent} with the {@link NfcEvent#nfcAdapter} field set
-         * @see #setNdefPushMessageCallback
          */
         public void onNdefPushComplete(NfcEvent event);
     }
@@ -504,11 +502,11 @@
     /**
      * A callback to be invoked when another NFC device capable of NDEF push (Android Beam)
      * is within range.
-     * <p>Implement this interface and pass it to {@link
+     * <p>Implement this interface and pass it to {@code
      * NfcAdapter#setNdefPushMessageCallback setNdefPushMessageCallback()} in order to create an
      * {@link NdefMessage} at the moment that another device is within range for NFC. Using this
      * callback allows you to create a message with data that might vary based on the
-     * content currently visible to the user. Alternatively, you can call {@link
+     * content currently visible to the user. Alternatively, you can call {@code
      * #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
      * same data.
      * @deprecated this feature is removed. File sharing can work using other technology like
diff --git a/core/java/android/os/PowerManagerInternal.java b/core/java/android/os/PowerManagerInternal.java
index 8afd6de..fec67b9 100644
--- a/core/java/android/os/PowerManagerInternal.java
+++ b/core/java/android/os/PowerManagerInternal.java
@@ -315,6 +315,13 @@
     public static final int MODE_DISPLAY_INACTIVE = 9;
 
     /**
+     * Mode: It indicates that display is changing layout due to rotation or fold
+     * unfold behavior.
+     * Defined in hardware/interfaces/power/aidl/android/hardware/power/Mode.aidl
+     */
+    public static final int MODE_DISPLAY_CHANGE = 17;
+
+    /**
      * SetPowerMode() is called to enable/disable specific hint mode, which
      * may result in adjustment of power/performance parameters of the
      * cpufreq governor and other controls on device side.
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 1294f98..5626b94 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -12,4 +12,11 @@
   namespace: "permissions"
   description: "enable voice activation permission APIs"
   bug: "287264308"
+}
+
+flag {
+    name: "role_controller_in_system_server"
+    namespace: "permissions"
+    description: "enable role controller in system server"
+    bug: "302562590"
 }
\ No newline at end of file
diff --git a/core/java/android/service/controls/Control.java b/core/java/android/service/controls/Control.java
index 3b757d6..33978be 100644
--- a/core/java/android/service/controls/Control.java
+++ b/core/java/android/service/controls/Control.java
@@ -50,7 +50,7 @@
  * and zone. Some of these values are defined by the user and/or the {@link ControlsProviderService}
  * and will be used to display the control as well as group them for management.
  * <p>
- * Each object will have an associated {@link DeviceTypes.DeviceType}. This will determine the icons and colors
+ * Each object will have an associated {@link DeviceTypes}. This will determine the icons and colors
  * used to display it.
  * <p>
  * An {@link Intent} linking to the provider Activity that expands on this {@link Control} and
@@ -420,7 +420,7 @@
      * This fixes the values relating to state of the {@link Control} as required by
      * {@link ControlsProviderService#createPublisherForAllAvailable}:
      * <ul>
-     *     <li> Status: {@link Status#STATUS_UNKNOWN}
+     *     <li> Status: {@link #STATUS_UNKNOWN}
      *     <li> Control template: {@link ControlTemplate#getNoTemplateObject}
      *     <li> Status text: {@code ""}
      *     <li> Auth Required: {@code true}
@@ -620,7 +620,7 @@
      *     <li> Device type: {@link DeviceTypes#TYPE_UNKNOWN}
      *     <li> Title: {@code ""}
      *     <li> Subtitle: {@code ""}
-     *     <li> Status: {@link Status#STATUS_UNKNOWN}
+     *     <li> Status: {@link #STATUS_UNKNOWN}
      *     <li> Control template: {@link ControlTemplate#getNoTemplateObject}
      *     <li> Status text: {@code ""}
      *     <li> Auth Required: {@code true}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index fce87db..0272bb9 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -155,7 +155,7 @@
      * The user has interacted with a Control. The action is dictated by the type of
      * {@link ControlAction} that was sent. A response can be sent via
      * {@link Consumer#accept}, with the Integer argument being one of the provided
-     * {@link ControlAction.ResponseResult}. The Integer should indicate whether the action
+     * {@link ControlAction} response results. The Integer should indicate whether the action
      * was received successfully, or if additional prompts should be presented to
      * the user. Any visual control updates should be sent via the Publisher.
 
diff --git a/core/java/android/service/controls/actions/ControlAction.java b/core/java/android/service/controls/actions/ControlAction.java
index 10f526d..4e38222 100644
--- a/core/java/android/service/controls/actions/ControlAction.java
+++ b/core/java/android/service/controls/actions/ControlAction.java
@@ -154,7 +154,7 @@
     public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5;
 
     /**
-     * The {@link ActionType} associated with this class.
+     * The action type associated with this class.
      */
     public abstract @ActionType int getActionType();
 
diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java
index 3902d6a..0dd950d 100644
--- a/core/java/android/service/controls/templates/ControlTemplate.java
+++ b/core/java/android/service/controls/templates/ControlTemplate.java
@@ -137,7 +137,7 @@
     }
 
     /**
-     * The {@link TemplateType} associated with this class.
+     * The template type associated with this class.
      */
     public abstract @TemplateType int getTemplateType();
 
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
new file mode 100644
index 0000000..b0ce1db
--- /dev/null
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.service.timezone."
+        }
+      ]
+    },
+    {
+      "name": "CtsLocationTimeZoneManagerHostTest"
+    }
+  ]
+}
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
index 9dca77e..31aeb9c 100644
--- a/core/java/android/service/voice/HotwordTrainingData.java
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -16,7 +16,6 @@
 
 package android.service.voice;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -34,7 +33,7 @@
  * Contains training data related to hotword detection service.
  *
  * <p>The constructed object's size must be within
- * {@link HotwordTrainingData#getMaxTrainingDataSize()} or an
+ * {@link HotwordTrainingData#getMaxTrainingDataBytes()} or an
  * {@link IllegalArgumentException} will be thrown on construction. Size of the object is calculated
  * by converting object to a {@link Parcel} and using the {@link Parcel#dataSize()}.
  *
@@ -49,63 +48,26 @@
         genToString = true)
 @SystemApi
 public final class HotwordTrainingData implements Parcelable {
-    /** Max size for hotword training data. */
-    public static int getMaxTrainingDataSize() {
+    /** Max size for hotword training data in bytes. */
+    public static int getMaxTrainingDataBytes() {
         return 1024 * 1024; // 1 MB;
     }
 
     /** The list containing hotword audio that is useful for training. */
     @NonNull
     @DataClass.PluralOf("trainingAudio")
-    private final List<HotwordTrainingAudio> mTrainingAudios;
+    private final List<HotwordTrainingAudio> mTrainingAudioList;
 
-    private static List<HotwordTrainingAudio> defaultTrainingAudios() {
+    private static List<HotwordTrainingAudio> defaultTrainingAudioList() {
         return Collections.emptyList();
     }
 
-    /** Timeout stage is unknown. */
-    public static final int TIMEOUT_STAGE_UNKNOWN = 0;
-
-    /**
-     * Timeout stage value that represents that the model timed out very early while detecting
-     * hotword.
-     */
-    public static final int TIMEOUT_STAGE_VERY_EARLY = 1;
-
-    /**
-     * Timeout stage value that represents that the model timed out early while detecting
-     * hotword.
-     */
-    public static final int TIMEOUT_STAGE_EARLY = 2;
-
-    /**
-     * Timeout stage value that represents that the model timed out in the middle while detecting
-     * hotword.
-     */
-    public static final int TIMEOUT_STAGE_MIDDLE = 3;
-
-    /**
-     * Timeout stage value that represents that the model timed out late while detecting
-     * hotword.
-     */
-    public static final int TIMEOUT_STAGE_LATE = 4;
-
-    /** @hide */
-    @IntDef(prefix = {"TIMEOUT_STAGE"}, value = {
-            TIMEOUT_STAGE_UNKNOWN,
-            TIMEOUT_STAGE_VERY_EARLY,
-            TIMEOUT_STAGE_EARLY,
-            TIMEOUT_STAGE_MIDDLE,
-            TIMEOUT_STAGE_LATE,
-    })
-    @interface HotwordTimeoutStage {}
-
-    /** Stage when timeout occurred. */
-    @HotwordTimeoutStage
+    /** App-defined stage when hotword model timed-out while running.
+     * <p> Returns 0 if unset. */
     private final int mTimeoutStage;
 
     private static int defaultTimeoutStage() {
-        return TIMEOUT_STAGE_UNKNOWN;
+        return 0;
     }
 
     private void onConstructed() {
@@ -115,10 +77,10 @@
         int dataSizeBytes = parcel.dataSize();
         parcel.recycle();
         Preconditions.checkArgument(
-                dataSizeBytes < getMaxTrainingDataSize(),
+                dataSizeBytes < getMaxTrainingDataBytes(),
                 TextUtils.formatSimple(
-                        "Hotword training data of size %s exceeds size limit of %s!",
-                        dataSizeBytes, getMaxTrainingDataSize()));
+                        "Hotword training data of size %s exceeds size limit of %s bytes!",
+                        dataSizeBytes, getMaxTrainingDataBytes()));
     }
 
 
@@ -136,46 +98,14 @@
     //@formatter:off
 
 
-    /** @hide */
-    @IntDef(prefix = "TIMEOUT_STAGE_", value = {
-        TIMEOUT_STAGE_UNKNOWN,
-        TIMEOUT_STAGE_VERY_EARLY,
-        TIMEOUT_STAGE_EARLY,
-        TIMEOUT_STAGE_MIDDLE,
-        TIMEOUT_STAGE_LATE
-    })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
-    @DataClass.Generated.Member
-    public @interface TimeoutStage {}
-
-    /** @hide */
-    @DataClass.Generated.Member
-    public static String timeoutStageToString(@TimeoutStage int value) {
-        switch (value) {
-            case TIMEOUT_STAGE_UNKNOWN:
-                    return "TIMEOUT_STAGE_UNKNOWN";
-            case TIMEOUT_STAGE_VERY_EARLY:
-                    return "TIMEOUT_STAGE_VERY_EARLY";
-            case TIMEOUT_STAGE_EARLY:
-                    return "TIMEOUT_STAGE_EARLY";
-            case TIMEOUT_STAGE_MIDDLE:
-                    return "TIMEOUT_STAGE_MIDDLE";
-            case TIMEOUT_STAGE_LATE:
-                    return "TIMEOUT_STAGE_LATE";
-            default: return Integer.toHexString(value);
-        }
-    }
-
     @DataClass.Generated.Member
     /* package-private */ HotwordTrainingData(
-            @NonNull List<HotwordTrainingAudio> trainingAudios,
-            @HotwordTimeoutStage int timeoutStage) {
-        this.mTrainingAudios = trainingAudios;
+            @NonNull List<HotwordTrainingAudio> trainingAudioList,
+            int timeoutStage) {
+        this.mTrainingAudioList = trainingAudioList;
         com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTrainingAudios);
+                NonNull.class, null, mTrainingAudioList);
         this.mTimeoutStage = timeoutStage;
-        com.android.internal.util.AnnotationValidations.validate(
-                HotwordTimeoutStage.class, null, mTimeoutStage);
 
         onConstructed();
     }
@@ -184,15 +114,16 @@
      * The list containing hotword audio that is useful for training.
      */
     @DataClass.Generated.Member
-    public @NonNull List<HotwordTrainingAudio> getTrainingAudios() {
-        return mTrainingAudios;
+    public @NonNull List<HotwordTrainingAudio> getTrainingAudioList() {
+        return mTrainingAudioList;
     }
 
     /**
-     * Stage when timeout occurred.
+     * App-defined stage when hotword model timed-out while running.
+     * <p> Returns 0 if unset.
      */
     @DataClass.Generated.Member
-    public @HotwordTimeoutStage int getTimeoutStage() {
+    public int getTimeoutStage() {
         return mTimeoutStage;
     }
 
@@ -203,7 +134,7 @@
         // String fieldNameToString() { ... }
 
         return "HotwordTrainingData { " +
-                "trainingAudios = " + mTrainingAudios + ", " +
+                "trainingAudioList = " + mTrainingAudioList + ", " +
                 "timeoutStage = " + mTimeoutStage +
         " }";
     }
@@ -221,7 +152,7 @@
         HotwordTrainingData that = (HotwordTrainingData) o;
         //noinspection PointlessBooleanExpression
         return true
-                && java.util.Objects.equals(mTrainingAudios, that.mTrainingAudios)
+                && java.util.Objects.equals(mTrainingAudioList, that.mTrainingAudioList)
                 && mTimeoutStage == that.mTimeoutStage;
     }
 
@@ -232,7 +163,7 @@
         // int fieldNameHashCode() { ... }
 
         int _hash = 1;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudios);
+        _hash = 31 * _hash + java.util.Objects.hashCode(mTrainingAudioList);
         _hash = 31 * _hash + mTimeoutStage;
         return _hash;
     }
@@ -243,7 +174,7 @@
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
-        dest.writeParcelableList(mTrainingAudios, flags);
+        dest.writeParcelableList(mTrainingAudioList, flags);
         dest.writeInt(mTimeoutStage);
     }
 
@@ -258,16 +189,14 @@
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
-        List<HotwordTrainingAudio> trainingAudios = new ArrayList<>();
-        in.readParcelableList(trainingAudios, HotwordTrainingAudio.class.getClassLoader());
+        List<HotwordTrainingAudio> trainingAudioList = new ArrayList<>();
+        in.readParcelableList(trainingAudioList, HotwordTrainingAudio.class.getClassLoader());
         int timeoutStage = in.readInt();
 
-        this.mTrainingAudios = trainingAudios;
+        this.mTrainingAudioList = trainingAudioList;
         com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTrainingAudios);
+                NonNull.class, null, mTrainingAudioList);
         this.mTimeoutStage = timeoutStage;
-        com.android.internal.util.AnnotationValidations.validate(
-                HotwordTimeoutStage.class, null, mTimeoutStage);
 
         onConstructed();
     }
@@ -293,8 +222,8 @@
     @DataClass.Generated.Member
     public static final class Builder {
 
-        private @NonNull List<HotwordTrainingAudio> mTrainingAudios;
-        private @HotwordTimeoutStage int mTimeoutStage;
+        private @NonNull List<HotwordTrainingAudio> mTrainingAudioList;
+        private int mTimeoutStage;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -305,26 +234,27 @@
          * The list containing hotword audio that is useful for training.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setTrainingAudios(@NonNull List<HotwordTrainingAudio> value) {
+        public @NonNull Builder setTrainingAudioList(@NonNull List<HotwordTrainingAudio> value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x1;
-            mTrainingAudios = value;
+            mTrainingAudioList = value;
             return this;
         }
 
-        /** @see #setTrainingAudios */
+        /** @see #setTrainingAudioList */
         @DataClass.Generated.Member
         public @NonNull Builder addTrainingAudio(@NonNull HotwordTrainingAudio value) {
-            if (mTrainingAudios == null) setTrainingAudios(new ArrayList<>());
-            mTrainingAudios.add(value);
+            if (mTrainingAudioList == null) setTrainingAudioList(new ArrayList<>());
+            mTrainingAudioList.add(value);
             return this;
         }
 
         /**
-         * Stage when timeout occurred.
+         * App-defined stage when hotword model timed-out while running.
+         * <p> Returns 0 if unset.
          */
         @DataClass.Generated.Member
-        public @NonNull Builder setTimeoutStage(@HotwordTimeoutStage int value) {
+        public @NonNull Builder setTimeoutStage(int value) {
             checkNotUsed();
             mBuilderFieldsSet |= 0x2;
             mTimeoutStage = value;
@@ -337,13 +267,13 @@
             mBuilderFieldsSet |= 0x4; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
-                mTrainingAudios = defaultTrainingAudios();
+                mTrainingAudioList = defaultTrainingAudioList();
             }
             if ((mBuilderFieldsSet & 0x2) == 0) {
                 mTimeoutStage = defaultTimeoutStage();
             }
             HotwordTrainingData o = new HotwordTrainingData(
-                    mTrainingAudios,
+                    mTrainingAudioList,
                     mTimeoutStage);
             return o;
         }
@@ -357,10 +287,10 @@
     }
 
     @DataClass.Generated(
-            time = 1693313864628L,
+            time = 1696092128091L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
-            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudios\npublic static final  int TIMEOUT_STAGE_UNKNOWN\npublic static final  int TIMEOUT_STAGE_VERY_EARLY\npublic static final  int TIMEOUT_STAGE_EARLY\npublic static final  int TIMEOUT_STAGE_MIDDLE\npublic static final  int TIMEOUT_STAGE_LATE\nprivate final @android.service.voice.HotwordTrainingData.HotwordTimeoutStage int mTimeoutStage\npublic static  int getMaxTrainingDataSize()\nprivate static  java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudios()\nprivate static  int defaultTimeoutStage()\nprivate  void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final  int mTimeoutStage\npublic static  int getMaxTrainingDataBytes()\nprivate static  java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static  int defaultTimeoutStage()\nprivate  void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 49e00d6..7befbfb 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -151,7 +151,7 @@
      */
     @NonNull public abstract List<String> onGetRequestedPackages();
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
+    private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
     @Nullable private RemoteCallback mCallback;
 
     @Override
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index cb488b0..c585734 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -26,6 +26,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
 import android.graphics.fonts.FontStyle;
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
@@ -528,6 +529,7 @@
         private final @NonNull List<Font> mFonts;
         private final @NonNull LocaleList mLocaleList;
         private final @Variant int mVariant;
+        private final int mVariableFontFamilyType;
 
         /** @hide */
         @Retention(SOURCE)
@@ -567,10 +569,11 @@
          * @hide Only system server can create this instance and passed via IPC.
          */
         public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
-                @Variant int variant) {
+                @Variant int variant, int variableFontFamilyType) {
             mFonts = fonts;
             mLocaleList = localeList;
             mVariant = variant;
+            mVariableFontFamilyType = variableFontFamilyType;
         }
 
         /**
@@ -621,6 +624,20 @@
             return mVariant;
         }
 
+        /**
+         * Returns the font family type.
+         *
+         * @see Builder#VARIABLE_FONT_FAMILY_TYPE_NONE
+         * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL
+         * @see Builder#VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY
+         * @see Builder#VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
+         * @hide
+         * @return variable font family type.
+         */
+        public @VariableFontFamilyType int getVariableFontFamilyType() {
+            return mVariableFontFamilyType;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -631,6 +648,7 @@
             dest.writeTypedList(mFonts, flags);
             dest.writeString8(mLocaleList.toLanguageTags());
             dest.writeInt(mVariant);
+            dest.writeInt(mVariableFontFamilyType);
         }
 
         public static final @NonNull Creator<FontFamily> CREATOR = new Creator<FontFamily>() {
@@ -641,8 +659,10 @@
                 source.readTypedList(fonts, Font.CREATOR);
                 String langTags = source.readString8();
                 int variant = source.readInt();
+                int varFamilyType = source.readInt();
 
-                return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
+                return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant,
+                        varFamilyType);
             }
 
             @Override
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index cdf5eec3..70e1896 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2197,6 +2197,9 @@
             float xOffset, float yOffset, float xPrecision, float yPrecision,
             long downTimeNanos, long eventTimeNanos,
             int pointerCount, PointerProperties[] pointerIds, PointerCoords[] pointerCoords) {
+        if (action == ACTION_CANCEL) {
+            flags |= FLAG_CANCELED;
+        }
         mNativePtr = nativeInitialize(mNativePtr, deviceId, source, displayId, action, flags,
                 edgeFlags, metaState, buttonState, classification, xOffset, yOffset,
                 xPrecision, yPrecision, downTimeNanos, eventTimeNanos, pointerCount, pointerIds,
@@ -2387,6 +2390,11 @@
         nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED);
     }
 
+    private void setCanceled(boolean canceled) {
+        final int flags = getFlags();
+        nativeSetFlags(mNativePtr, canceled ? flags | FLAG_CANCELED : flags & ~FLAG_CANCELED);
+    }
+
     /** @hide */
     public  boolean isTargetAccessibilityFocus() {
         final int flags = getFlags();
@@ -3510,6 +3518,14 @@
      * Sets this event's action.
      */
     public final void setAction(int action) {
+        final int actionMasked = action & ACTION_MASK;
+        if (actionMasked == ACTION_CANCEL) {
+            setCanceled(true);
+        } else if (actionMasked == ACTION_POINTER_UP) {
+            // Do nothing - we don't know what the real intent here is
+        } else {
+            setCanceled(false);
+        }
         nativeSetAction(mNativePtr, action);
     }
 
@@ -4157,6 +4173,7 @@
     /** @hide */
     @Override
     public final void cancel() {
+        setCanceled(true);
         setAction(ACTION_CANCEL);
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f421351..afa3157 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3858,7 +3858,8 @@
                 mPendingTransitions.clear();
             }
 
-            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+            handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+                    "view not visible");
         } else if (cancelAndRedraw) {
             mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
                 ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
@@ -3873,7 +3874,8 @@
                 mPendingTransitions.clear();
             }
             if (!performDraw(mActiveSurfaceSyncGroup)) {
-                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+                handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+                        mLastPerformDrawSkippedReason);
             }
         }
 
@@ -4665,6 +4667,10 @@
 
                 return didProduceBuffer -> {
                     if (!didProduceBuffer) {
+                        Trace.instant(Trace.TRACE_TAG_VIEW,
+                                "Transaction not synced due to no frame drawn-" + mTag);
+                        Log.d(mTag, "Pending transaction will not be applied in sync with a draw "
+                                + "because there was nothing new to draw");
                         mBlastBufferQueue.applyPendingTransactions(frame);
                     }
                 };
@@ -4687,8 +4693,7 @@
             return false;
         }
 
-        final boolean fullRedrawNeeded =
-                mFullRedrawNeeded || surfaceSyncGroup != null || mHasPendingTransactions;
+        final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
         mFullRedrawNeeded = false;
 
         mIsDrawing = true;
@@ -4748,7 +4753,8 @@
             if (mSurfaceHolder != null && mSurface.isValid()) {
                 usingAsyncReport = true;
                 SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
-                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction);
+                    handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
+                            "SurfaceHolder");
                 });
 
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4762,7 +4768,8 @@
         }
 
         if (!usingAsyncReport) {
-            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction);
+            handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction,
+                    "no async report");
         }
 
         if (mPerformContentCapture) {
@@ -4772,13 +4779,19 @@
     }
 
     private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup,
-            @Nullable Transaction pendingTransaction) {
+            @Nullable Transaction pendingTransaction, String logReason) {
         if (surfaceSyncGroup != null) {
             if (pendingTransaction != null) {
                 surfaceSyncGroup.addTransaction(pendingTransaction);
             }
             surfaceSyncGroup.markSyncReady();
         } else if (pendingTransaction != null) {
+            Trace.instant(Trace.TRACE_TAG_VIEW,
+                    "Transaction not synced due to " + logReason + "-" + mTag);
+            if (DEBUG_BLAST) {
+                Log.d(mTag, "Pending transaction will not be applied in sync with a draw due to "
+                        + logReason);
+            }
             pendingTransaction.apply();
         }
     }
@@ -8993,7 +9006,8 @@
             mAdded = false;
             AnimationHandler.removeRequestor(this);
         }
-        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction);
+        handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction,
+                "shutting down VRI");
         WindowManagerGlobal.getInstance().doRemoveView(this);
     }
 
@@ -11362,15 +11376,15 @@
     @Override
     public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
         if (mRemoved || !isHardwareEnabled()) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag);
+            Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately");
             t.apply();
         } else {
+            Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag);
             // Copy and clear the passed in transaction for thread safety. The new transaction is
             // accessed on the render thread.
             mPendingTransaction.merge(t);
             mHasPendingTransactions = true;
-            // Schedule the traversal to ensure there's an attempt to draw a frame and apply the
-            // pending transactions. This is also where the registerFrameCallback will be scheduled.
-            scheduleTraversals();
         }
         return true;
     }
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index f543cab..334c2b77 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -255,6 +255,16 @@
 
     // END AUTOFILL PCC CLASSIFICATION FLAGS
 
+    /**
+     * Define the max input length for autofill to show suggesiton UI
+     *
+     * E.g. if flag is set to 3, autofill will only show suggestions when user inputs less than 3
+     * characters
+     *
+     * @hide
+     */
+    public static final String DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL =
+            "max_input_length_for_autofill";
 
     /**
      * Sets a value of delay time to show up the inline tooltip view.
@@ -295,6 +305,10 @@
             DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true;
     // END AUTOFILL FOR ALL APPS DEFAULTS
 
+    /**
+     * @hide
+     */
+    public static final int DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL = 3;
     private AutofillFeatureFlags() {};
 
     /**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 6cf185a..89fa83e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3443,7 +3443,7 @@
             return false;
         }
         for (String hint : hints) {
-            if (hint.equals(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+            if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
                 return true;
             }
         }
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
new file mode 100644
index 0000000..7e06f87
--- /dev/null
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.view.contentprotection.flags"
+
+flag {
+    name: "blocklist_update_enabled"
+    namespace: "content_protection"
+    description: "If true, content protection blocklist is mutable and can be updated."
+    bug: "301658008"
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index 195565c..33db671 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -551,8 +551,8 @@
          * Sets the entity types. e.g. {@link TextClassifier#TYPE_ADDRESS}.
          * <p>
          * Supported types:
-         * <p>See {@link TextClassifier.EntityType}
-         * <p>See {@link ConversationAction.ActionType}
+         * <p>See {@link TextClassifier} types
+         * <p>See {@link ConversationAction} types
          * <p>See {@link ULocale#toLanguageTag()}
          */
         @NonNull
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
index b7e13dd..52760f7 100644
--- a/core/java/android/view/translation/TranslationCapability.java
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -207,7 +207,7 @@
 
     /**
      * Translation flags for settings that are supported by the
-     * {@link android.service.translation.TranslationService} between the {@link TranslationSpec}s
+     * translation service between the {@link TranslationSpec}s
      * provided in this capability.
      */
     @DataClass.Generated.Member
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 5aad823..99544e8 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -56,7 +56,7 @@
  * translation framework.
  *
  * <p>The TranslationManager manages {@link Translator}s and help bridge client calls to
- * the server {@link android.service.translation.TranslationService} </p>
+ * the server translation service </p>
  */
 @SystemService(Context.TRANSLATION_MANAGER_SERVICE)
 public final class TranslationManager {
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index 027edc2..ff11ffa 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -27,7 +27,7 @@
 import java.util.List;
 
 /**
- * Translation request sent to the {@link android.service.translation.TranslationService} by the
+ * Translation request sent to the translation service by the
  * {@link android.view.translation.Translator} which contains the text to be translated.
  */
 @DataClass(genToString = true, genHiddenConstDefs = true, genBuilder = true)
diff --git a/core/java/android/view/translation/TranslationResponse.java b/core/java/android/view/translation/TranslationResponse.java
index b77f2e2..3362fc0 100644
--- a/core/java/android/view/translation/TranslationResponse.java
+++ b/core/java/android/view/translation/TranslationResponse.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.service.translation.TranslationService;
 import android.util.SparseArray;
 
 import com.android.internal.util.DataClass;
@@ -30,17 +29,17 @@
 import java.util.Objects;
 
 /**
- * Response from the {@link TranslationService}, which contains the translated result.
+ * Response from the translation service, which contains the translated result.
  */
 @DataClass(genBuilder = true, genToString = true, genHiddenConstDefs = true)
 public final class TranslationResponse implements Parcelable {
 
     /**
-     * The {@link TranslationService} was successful in translating.
+     * The translation service was successful in translating.
      */
     public static final int TRANSLATION_STATUS_SUCCESS = 0;
     /**
-     * The {@link TranslationService} returned unknown translation result.
+     * The translation service returned unknown translation result.
      */
     public static final int TRANSLATION_STATUS_UNKNOWN_ERROR = 1;
     /**
diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java
index 9dff2d5..18a240d 100644
--- a/core/java/android/view/translation/TranslationResponseValue.java
+++ b/core/java/android/view/translation/TranslationResponseValue.java
@@ -27,7 +27,7 @@
 import java.util.Objects;
 
 /**
- * A translated response value from {@link android.service.translation.TranslationService}.
+ * A translated response value from translation service.
  */
 @DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true,
         genHiddenConstDefs = true)
diff --git a/core/java/android/view/translation/ViewTranslationRequest.java b/core/java/android/view/translation/ViewTranslationRequest.java
index a41749a..54b8ac2 100644
--- a/core/java/android/view/translation/ViewTranslationRequest.java
+++ b/core/java/android/view/translation/ViewTranslationRequest.java
@@ -33,7 +33,7 @@
 
 /**
  * Wrapper class representing a translation request associated with a {@link android.view.View} to
- * be used by {@link android.service.translation.TranslationService}.
+ * be used by translation service.
  */
 @DataClass(genBuilder = false, genToString = true, genEqualsHashCode = true, genGetters = false,
         genHiddenConstructor = true, genHiddenConstDefs = true)
diff --git a/core/java/android/view/translation/ViewTranslationResponse.java b/core/java/android/view/translation/ViewTranslationResponse.java
index d993114..134ff5a 100644
--- a/core/java/android/view/translation/ViewTranslationResponse.java
+++ b/core/java/android/view/translation/ViewTranslationResponse.java
@@ -33,7 +33,7 @@
 
 /**
  * Wrapper class representing a translation response associated with a {@link android.view.View} to
- * be used by {@link android.service.translation.TranslationService}.
+ * be used by translation service.
  */
 @DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true, genGetters = false)
 public final class ViewTranslationResponse implements Parcelable {
diff --git a/core/tests/coretests/src/android/app/time/TEST_MAPPING b/core/tests/coretests/src/android/app/time/TEST_MAPPING
new file mode 100644
index 0000000..9d711a2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.time."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING b/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING
new file mode 100644
index 0000000..6c4d48d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timedetector/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.timedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING b/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING
new file mode 100644
index 0000000..8872f64
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timezonedetector/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.app.timezonedetector."
+        }
+      ]
+    }
+  ]
+}
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index d46f762..4dd5889 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -28,6 +28,7 @@
 import static junit.framework.Assert.fail;
 
 import android.graphics.fonts.FontCustomizationParser;
+import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.FontStyle;
 import android.os.LocaleList;
 import android.text.FontConfig;
@@ -64,7 +65,8 @@
                 Collections.singletonList(new FontConfig.FontFamily(
                     Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
                         new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
-                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
+                        FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
         FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
@@ -84,7 +86,8 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", "serif")),
-                LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
+                LocaleList.forLanguageTags("en"), VARIANT_DEFAULT,
+                FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -101,7 +104,8 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", null)),
-                LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
+                LocaleList.forLanguageTags("en"), VARIANT_COMPACT,
+                FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -118,7 +122,8 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", null)),
-                LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
+                LocaleList.forLanguageTags("en"), VARIANT_ELEGANT,
+                FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -140,7 +145,8 @@
                         new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
                       new FontConfig.Font(new File("italic.ttf"), null, "test",
                         new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
-                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
+                        FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
         FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
@@ -166,7 +172,8 @@
                         new FontConfig.Font(new File("test-VF.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "'wdth' 400.0,'wght' 700.0", null)),
-                        LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+                        LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
+                        FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
                 "sans-serif");
         FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -187,7 +194,8 @@
                         new FontConfig.Font(new File("test.ttc"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 1, "", null)),
-                                LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+                                LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
+                        FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)),
                 "sans-serif");
         FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -206,7 +214,8 @@
                         new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
                       new FontConfig.Font(new File("test.ttc"), null, "test",
                         new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
-                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT,
+                        FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE)), "sans-serif");
         FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
@@ -372,6 +381,20 @@
                 .isEqualTo("emoji.ttf");
     }
 
+    @Test
+    public void varFamilyType() throws Exception {
+        String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family name='sans-serif' varFamilyType='1'>"
+                + "    <font>test.ttf</font>"
+                + "  </family>"
+                + "</familyset>";
+        FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+        List<FontConfig.FontFamily> families = config.getFontFamilies();
+        assertThat(families.size()).isEqualTo(1);  // legacy one should be ignored.
+        assertThat(families.get(0).getVariableFontFamilyType()).isEqualTo(1);
+    }
+
     private FontConfig readFamilies(String xml, boolean allowNonExisting)
             throws IOException, XmlPullParserException {
         ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/core/tests/coretests/src/android/service/timezone/TEST_MAPPING b/core/tests/coretests/src/android/service/timezone/TEST_MAPPING
new file mode 100644
index 0000000..46f476f
--- /dev/null
+++ b/core/tests/coretests/src/android/service/timezone/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+  "postsubmit": [
+    {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.service.timezone."
+        }
+      ]
+    }
+  ]
+}
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 1e97fce..02e032b 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -15,96 +15,9 @@
 -->
 <familyset version="23">
     <!-- first font is default -->
-    <family name="sans-serif">
-        <font weight="100" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
+    <family name="sans-serif" varFamilyType="2">
+        <font>Roboto-Regular.ttf
           <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="normal">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-        <font weight="100" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="italic">Roboto-Regular.ttf
-          <axis tag="ital" stylevalue="1" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
         </font>
    </family>
 
@@ -119,96 +32,9 @@
     <alias name="tahoma" to="sans-serif" />
     <alias name="verdana" to="sans-serif" />
 
-    <family name="sans-serif-condensed">
-      <font weight="100" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
+    <family name="sans-serif-condensed" varFamilyType="2">
+      <font>Roboto-Regular.ttf
         <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="100" />
-      </font>
-      <font weight="200" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="200" />
-      </font>
-      <font weight="300" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="300" />
-      </font>
-      <font weight="400" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="500" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="500" />
-      </font>
-      <font weight="600" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="600" />
-      </font>
-      <font weight="700" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="700" />
-      </font>
-      <font weight="800" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="800" />
-      </font>
-      <font weight="900" style="normal">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="0" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="900" />
-      </font>
-      <font weight="100" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="100" />
-      </font>
-      <font weight="200" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="200" />
-      </font>
-      <font weight="300" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="300" />
-      </font>
-      <font weight="400" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="500" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="500" />
-      </font>
-      <font weight="600" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="600" />
-      </font>
-      <font weight="700" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="700" />
-      </font>
-      <font weight="800" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="800" />
-      </font>
-      <font weight="900" style="italic">Roboto-Regular.ttf
-        <axis tag="ital" stylevalue="1" />
-        <axis tag="wdth" stylevalue="75" />
-        <axis tag="wght" stylevalue="900" />
       </font>
     </family>
     <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
@@ -246,13 +72,8 @@
         <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
     </family>
 
-    <family name="cursive">
-      <font weight="400" style="normal">DancingScript-Regular.ttf
-        <axis tag="wght" stylevalue="400" />
-      </font>
-      <font weight="700" style="normal">DancingScript-Regular.ttf
-        <axis tag="wght" stylevalue="700" />
-      </font>
+    <family name="cursive" varFamilyType="1">
+      <font>DancingScript-Regular.ttf</font>
     </family>
 
     <family name="sans-serif-smallcaps">
@@ -269,96 +90,9 @@
     </family>
     <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
 
-    <family name="roboto-flex">
-        <font weight="100" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
+    <family name="roboto-flex" varFamilyType="2">
+        <font>RobotoFlex-Regular.ttf
           <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="normal">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="0" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
-        </font>
-        <font weight="100" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="100" />
-        </font>
-        <font weight="200" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="200" />
-        </font>
-        <font weight="300" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="300" />
-        </font>
-        <font weight="400" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="400" />
-        </font>
-        <font weight="500" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="500" />
-        </font>
-        <font weight="600" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="600" />
-        </font>
-        <font weight="700" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="700" />
-        </font>
-        <font weight="800" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="800" />
-        </font>
-        <font weight="900" style="italic">RobotoFlex-Regular.ttf
-          <axis tag="slnt" stylevalue="-10" />
-          <axis tag="wdth" stylevalue="100" />
-          <axis tag="wght" stylevalue="900" />
         </font>
     </family>
 
@@ -375,38 +109,12 @@
         </font>
         <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
     </family>
-    <family lang="und-Ethi">
-        <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+    <family lang="und-Ethi" varFamilyType="1">
+        <font postScriptName="NotoSansEthiopic-Regular">
             NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
-            NotoSansEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+            NotoSerifEthiopic-VF.ttf
         </font>
     </family>
     <family lang="und-Hebr">
@@ -432,124 +140,33 @@
         </font>
         <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
     </family>
-    <family lang="und-Armn">
-        <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+    <family lang="und-Armn" varFamilyType="1">
+        <font postScriptName="NotoSansArmenian-Regular">
             NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
-            NotoSansArmenian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+            NotoSerifArmenian-VF.ttf
         </font>
     </family>
-    <family lang="und-Geor,und-Geok">
-        <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+    <family lang="und-Geor,und-Geok" varFamilyType="1">
+        <font postScriptName="NotoSansGeorgian-Regular">
             NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
-            NotoSansGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+            NotoSerifGeorgian-VF.ttf
         </font>
     </family>
-    <family lang="und-Deva" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+    <family lang="und-Deva" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagari-Regular">
             NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
-            NotoSansDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+            NotoSerifDevanagari-VF.ttf
         </font>
     </family>
-    <family lang="und-Deva" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+    <family lang="und-Deva" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansDevanagariUI-Regular">
             NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
-            NotoSansDevanagariUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
 
@@ -584,316 +201,82 @@
         </font>
         <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
     </family>
-    <family lang="und-Guru" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+    <family lang="und-Guru" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhi-Regular">
             NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
-            NotoSansGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+            NotoSerifGurmukhi-VF.ttf
         </font>
     </family>
-    <family lang="und-Guru" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+    <family lang="und-Guru" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansGurmukhiUI-Regular">
             NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
-            NotoSansGurmukhiUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Taml" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+    <family lang="und-Taml" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTamil-Regular">
             NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
-            NotoSansTamil-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+            NotoSerifTamil-VF.ttf
         </font>
     </family>
-    <family lang="und-Taml" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+    <family lang="und-Taml" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTamilUI-Regular">
             NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
-            NotoSansTamilUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Mlym" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+    <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalam-Regular">
             NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
-            NotoSansMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+            NotoSerifMalayalam-VF.ttf
         </font>
     </family>
-    <family lang="und-Mlym" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+    <family lang="und-Mlym" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansMalayalamUI-Regular">
             NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
-            NotoSansMalayalamUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Beng" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+    <family lang="und-Beng" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansBengali-Regular">
             NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
-            NotoSansBengali-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+            NotoSerifBengali-VF.ttf
         </font>
     </family>
-    <family lang="und-Beng" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+    <family lang="und-Beng" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansBengaliUI-Regular">
             NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
-            NotoSansBengaliUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Telu" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+    <family lang="und-Telu" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansTelugu-Regular">
             NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
-            NotoSansTelugu-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+            NotoSerifTelugu-VF.ttf
         </font>
     </family>
-    <family lang="und-Telu" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+    <family lang="und-Telu" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansTeluguUI-Regular">
             NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
-            NotoSansTeluguUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Knda" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+    <family lang="und-Knda" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansKannada-Regular">
             NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
-            NotoSansKannada-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+            NotoSerifKannada-VF.ttf
         </font>
     </family>
-    <family lang="und-Knda" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+    <family lang="und-Knda" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansKannadaUI-Regular">
             NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
-            NotoSansKannadaUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
     <family lang="und-Orya" variant="elegant">
@@ -907,56 +290,17 @@
         </font>
         <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
     </family>
-    <family lang="und-Sinh" variant="elegant">
-        <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+    <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+        <font postScriptName="NotoSansSinhala-Regular">
             NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
-            NotoSansSinhala-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
-        <font weight="400" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" fallbackFor="serif"
-              postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
+        <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+            NotoSerifSinhala-VF.ttf
         </font>
     </family>
-    <family lang="und-Sinh" variant="compact">
-        <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+    <family lang="und-Sinh" variant="compact" varFamilyType="1">
+        <font postScriptName="NotoSansSinhalaUI-Regular">
             NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
-            NotoSansSinhalaUI-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
     <family lang="und-Khmr" variant="elegant">
@@ -1054,22 +398,9 @@
     <family lang="und-Ahom">
         <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
     </family>
-    <family lang="und-Adlm">
-        <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+    <family lang="und-Adlm" varFamilyType="1">
+        <font postScriptName="NotoSansAdlam-Regular">
             NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
-            NotoSansAdlam-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
     <family lang="und-Avst">
@@ -1355,22 +686,9 @@
             NotoSansTaiViet-Regular.ttf
         </font>
     </family>
-    <family lang="und-Tibt">
-        <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+    <family lang="und-Tibt" varFamilyType="1">
+        <font postScriptName="NotoSerifTibetan-Regular">
             NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
-            NotoSerifTibetan-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
     <family lang="und-Tfng">
@@ -1537,94 +855,29 @@
     <family lang="und-Dogr">
         <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
     </family>
-    <family lang="und-Medf">
-        <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+    <family lang="und-Medf" varFamilyType="1">
+        <font postScriptName="NotoSansMedefaidrin-Regular">
             NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
-            NotoSansMedefaidrin-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Soyo">
-        <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+    <family lang="und-Soyo" varFamilyType="1">
+        <font postScriptName="NotoSansSoyombo-Regular">
             NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
-            NotoSansSoyombo-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Takr">
-        <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+    <family lang="und-Takr" varFamilyType="1">
+        <font postScriptName="NotoSansTakri-Regular">
             NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
-            NotoSansTakri-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Hmnp">
-        <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+    <family lang="und-Hmnp" varFamilyType="1">
+        <font postScriptName="NotoSerifHmongNyiakeng-Regular">
             NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
-            NotoSerifNyiakengPuachueHmong-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
-    <family lang="und-Yezi">
-        <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+    <family lang="und-Yezi" varFamilyType="1">
+        <font postScriptName="NotoSerifYezidi-Regular">
             NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="400"/>
-        </font>
-        <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="500"/>
-        </font>
-        <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="600"/>
-        </font>
-        <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
-            NotoSerifYezidi-VF.ttf
-            <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
 </familyset>
diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl
index 354f10a..87cb942 100644
--- a/data/keyboards/Vendor_0957_Product_0001.kl
+++ b/data/keyboards/Vendor_0957_Product_0001.kl
@@ -47,7 +47,6 @@
 
 # custom keys
 key usage 0x000c01BB    TV_INPUT
-key usage 0x000c0186    MACRO_1     WAKE
 
 key usage 0x000c0185    TV_TELETEXT
 key usage 0x000c0061    CAPTIONS
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 1ff5a3d..250362b 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -479,7 +479,8 @@
          * This configuration may be useful when using opaque bitmaps
          * that do not require high color fidelity.
          *
-         * <p>Use this formula to pack into 16 bits:</p>
+         * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+         *    use this formula to pack into 16 bits:</p>
          * <pre class="prettyprint">
          * short color = (R & 0x1f) << 11 | (G & 0x3f) << 5 | (B & 0x1f);
          * </pre>
@@ -516,7 +517,8 @@
          * This configuration is very flexible and offers the best
          * quality. It should be used whenever possible.
          *
-         * <p>Use this formula to pack into 32 bits:</p>
+         * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+         *    use this formula to pack into 32 bits:</p>
          * <pre class="prettyprint">
          * int color = (A & 0xff) << 24 | (B & 0xff) << 16 | (G & 0xff) << 8 | (R & 0xff);
          * </pre>
@@ -531,7 +533,8 @@
          * This configuration is particularly suited for wide-gamut and
          * HDR content.
          *
-         * <p>Use this formula to pack into 64 bits:</p>
+         * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+         *    use this formula to pack into 64 bits:</p>
          * <pre class="prettyprint">
          * long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff);
          * </pre>
@@ -556,7 +559,8 @@
          * blending, such that the memory cost is the same as ARGB_8888 while enabling higher color
          * precision.
          *
-         * <p>Use this formula to pack into 32 bits:</p>
+         * <p>When accessing directly via #copyPixelsFromBuffer or #copyPixelsToBuffer,
+         *  use this formula to pack into 32 bits:</p>
          * <pre class="prettyprint">
          * int color = (A & 0x3) << 30 | (B & 0x3ff) << 20 | (G & 0x3ff) << 10 | (R & 0x3ff);
          * </pre>
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 5c06577..dcfff62 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -16,9 +16,13 @@
 
 package android.graphics;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.graphics.hwui.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -32,6 +36,7 @@
      * Prevent garbage collection.
      */
     /*package*/ Bitmap mBitmap;
+    private Gainmap mOverrideGainmap;
 
     private int mTileX;
     private int mTileY;
@@ -173,6 +178,24 @@
     }
 
     /**
+     * Draws the BitmapShader with a copy of the given gainmap instead of the gainmap on the Bitmap
+     * the shader was constructed from
+     *
+     * @param overrideGainmap The gainmap to draw instead, null to use any gainmap on the Bitmap
+     */
+    @FlaggedApi(Flags.FLAG_GAINMAP_ANIMATIONS)
+    public void setOverrideGainmap(@Nullable Gainmap overrideGainmap) {
+        if (!Flags.gainmapAnimations()) throw new IllegalStateException("API not available");
+
+        if (overrideGainmap == null) {
+            mOverrideGainmap = null;
+        } else {
+            mOverrideGainmap = new Gainmap(overrideGainmap, overrideGainmap.getGainmapContents());
+        }
+        discardNativeInstance();
+    }
+
+    /**
      * Returns the current max anisotropic filtering value configured by
      * {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero.
      */
@@ -199,14 +222,9 @@
 
         mIsDirectSampled = mRequestDirectSampling;
         mRequestDirectSampling = false;
-
-        if (mMaxAniso > 0) {
-            return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX,
-                    mTileY, mMaxAniso, mIsDirectSampled);
-        } else {
-            return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY,
-                    enableLinearFilter, mIsDirectSampled);
-        }
+        return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX,
+                mTileY, mMaxAniso, enableLinearFilter, mIsDirectSampled,
+                mOverrideGainmap != null ? mOverrideGainmap.mNativePtr : 0);
     }
 
     /** @hide */
@@ -217,9 +235,7 @@
     }
 
     private static native long nativeCreate(long nativeMatrix, long bitmapHandle,
-            int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled);
-
-    private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle,
-            int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled);
+            int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean filter,
+            boolean isDirectSampled, long overrideGainmapHandle);
 }
 
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 674246a..735bc18 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,10 @@
 
 package android.graphics;
 
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY;
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT;
 import static android.text.FontConfig.NamedFamilyList;
 
 import android.annotation.NonNull;
@@ -28,6 +32,7 @@
 import android.os.LocaleList;
 import android.text.FontConfig;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Xml;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -256,6 +261,7 @@
         final String lang = parser.getAttributeValue("", "lang");
         final String variant = parser.getAttributeValue(null, "variant");
         final String ignore = parser.getAttributeValue(null, "ignore");
+        final String varFamilyTypeStr = parser.getAttributeValue(null, "varFamilyType");
         final List<FontConfig.Font> fonts = new ArrayList<>();
         while (keepReading(parser)) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -278,12 +284,45 @@
                 intVariant = FontConfig.FontFamily.VARIANT_ELEGANT;
             }
         }
+        int varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+        if (varFamilyTypeStr != null) {
+            varFamilyType = Integer.parseInt(varFamilyTypeStr);
+            if (varFamilyType <= -1 || varFamilyType > 3) {
+                Log.e(TAG, "Error: unexpected varFamilyType value: " + varFamilyTypeStr);
+                varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+            }
+
+            // validation but don't read font content for performance reasons.
+            switch (varFamilyType) {
+                case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY:
+                    if (fonts.size() != 1) {
+                        Log.e(TAG, "Error: Single font support wght axis, but two or more fonts are"
+                                + " included in the font family.");
+                        varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+                    }
+                    break;
+                case VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL:
+                    if (fonts.size() != 1) {
+                        Log.e(TAG, "Error: Single font support both ital and wght axes, but two or"
+                                + " more fonts are included in the font family.");
+                        varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+                    }
+                    break;
+                case VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT:
+                    if (fonts.size() != 2) {
+                        Log.e(TAG, "Error: two fonts that support wght axis, but one or three or"
+                                + " more fonts are included in the font family.");
+                        varFamilyType = VARIABLE_FONT_FAMILY_TYPE_NONE;
+                    }
+            }
+        }
 
         boolean skip = (ignore != null && (ignore.equals("true") || ignore.equals("1")));
         if (skip || fonts.isEmpty()) {
             return null;
         }
-        return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
+        return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant,
+                varFamilyType);
     }
 
     private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 5e41105..4c75356 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -18,7 +18,10 @@
 
 import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,6 +35,7 @@
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Set;
 
@@ -184,32 +188,59 @@
         }
 
         /**
+         * A special variable font family type that indicates `analyzeAndResolveVariableType` could
+         * not be identified the variable font family type.
+         *
          * @see #buildVariableFamily()
          * @hide
          */
         public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1;
 
         /**
+         * A variable font family type that indicates no variable font family can be used.
+         *
+         * The font family is used as bundle of static fonts.
          * @see #buildVariableFamily()
          * @hide
          */
         public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0;
         /**
+         * A variable font family type that indicates single font file can be used for multiple
+         * weight. For the italic style, fake italic may be applied.
+         *
          * @see #buildVariableFamily()
          * @hide
          */
         public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1;
         /**
+         * A variable font family type that indicates single font file can be used for multiple
+         * weight and italic.
+         *
          * @see #buildVariableFamily()
          * @hide
          */
         public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2;
         /**
+         * A variable font family type that indicates two font files are included in the family:
+         * one can be used for upright with various weights, the other one can be used for italic
+         * with various weights.
+         *
          * @see #buildVariableFamily()
          * @hide
          */
         public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3;
 
+        /** @hide */
+        @Retention(SOURCE)
+        @IntDef(prefix = { "VARIABLE_FONT_FAMILY_TYPE_" }, value = {
+                VARIABLE_FONT_FAMILY_TYPE_UNKNOWN,
+                VARIABLE_FONT_FAMILY_TYPE_NONE,
+                VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY,
+                VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL,
+                VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT
+        })
+        public @interface VariableFontFamilyType {}
+
         /**
          * The registered italic axis used for adjusting requested style.
          * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital
@@ -222,7 +253,9 @@
          */
         private static final int TAG_wght = 0x77676874;  // w(0x77), g(0x67), h(0x68), t(0x74)
 
-        private static int analyzeAndResolveVariableType(ArrayList<Font> fonts) {
+        /** @hide */
+        public static @VariableFontFamilyType int analyzeAndResolveVariableType(
+                ArrayList<Font> fonts) {
             if (fonts.size() > 2) {
                 return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN;
             }
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 9810022..d4e35b3 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -121,7 +121,8 @@
 
 
         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
-                defaultFonts, languageTags, variant, false, cache);
+                defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
+                cache);
         // Insert family into fallback map.
         for (int i = 0; i < fallbackMap.size(); i++) {
             final String name = fallbackMap.keyAt(i);
@@ -138,8 +139,8 @@
                     familyListSet.familyList.add(defaultFamily);
                 }
             } else {
-                final FontFamily family = createFontFamily(fallback, languageTags, variant, false,
-                        cache);
+                final FontFamily family = createFontFamily(fallback, languageTags, variant,
+                        xmlFamily.getVariableFontFamilyType(), false, cache);
                 if (family != null) {
                     familyListSet.familyList.add(family);
                 } else if (defaultFamily != null) {
@@ -155,6 +156,7 @@
             @NonNull List<FontConfig.Font> fonts,
             @NonNull String languageTags,
             @FontConfig.FontFamily.Variant int variant,
+            int varFamilyType,
             boolean isDefaultFallback,
             @NonNull Map<String, ByteBuffer> cache) {
         if (fonts.size() == 0) {
@@ -196,7 +198,7 @@
             }
         }
         return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
-                isDefaultFallback, FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
+                isDefaultFallback, varFamilyType);
     }
 
     private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
@@ -210,6 +212,7 @@
             final FontFamily family = createFontFamily(
                     xmlFamily.getFontList(),
                     xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
+                    xmlFamily.getVariableFontFamilyType(),
                     true, // named family is always default
                     bufferCache);
             if (family == null) {
@@ -291,6 +294,7 @@
             int configVersion
     ) {
         try {
+            Log.i(TAG, "Loading font config from " + fontsXml);
             return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir,
                                                 updatableFontMap, lastModifiedDate, configVersion);
         } catch (IOException e) {
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 f259902..dddcbd4 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
@@ -512,6 +512,7 @@
      * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
      */
     public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+        mBubbleProperties.refresh();
         if (canShowAsBubbleBar() && listener != null) {
             // Only set the listener if we can show the bubble bar.
             mBubbleStateListener = listener;
@@ -529,6 +530,7 @@
      * will be updated accordingly.
      */
     public void unregisterBubbleStateListener() {
+        mBubbleProperties.refresh();
         if (mBubbleStateListener != null) {
             mBubbleStateListener = null;
             setUpBubbleViewsForMode();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
index 85aaa8e..4206d93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
@@ -29,4 +29,7 @@
      * When this is `false`, bubbles will be floating.
      */
     val isBubbleBarEnabled: Boolean
+
+    /** Refreshes the current value of [isBubbleBarEnabled]. */
+    fun refresh()
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
index 9d8b9a6..e1dea3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
@@ -22,6 +22,13 @@
 object ProdBubbleProperties : BubbleProperties {
 
     // TODO(b/256873975) Should use proper flag when available to shell/launcher
-    override val isBubbleBarEnabled =
-        SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+    private var _isBubbleBarEnabled =
+            SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+
+    override val isBubbleBarEnabled
+        get() = _isBubbleBarEnabled
+
+    override fun refresh() {
+        _isBubbleBarEnabled = SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 26b5a50..63cdb4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -67,6 +67,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 
@@ -484,7 +485,7 @@
     }
 
     /** Updates divide position and split bounds base on the ratio within root bounds. */
-    public void setDivideRatio(@SnapPosition int snapPosition) {
+    public void setDivideRatio(@PersistentSnapPosition int snapPosition) {
         final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget(
                 snapPosition);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index ff38b7e..e734300 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -26,6 +26,16 @@
 
 /** Helper utility class of methods and constants that are available to be imported in Launcher. */
 public class SplitScreenConstants {
+    /**
+     * Duration used for every split fade-in or fade-out.
+     */
+    public static final int FADE_DURATION = 133;
+
+    ///////////////
+    // IMPORTANT for the following SPLIT_POSITION and SNAP_TO constants:
+    // These int values must not be changed -- they are persisted to user-defined app pairs, and
+    // will break things if changed.
+    //
 
     /**
      * Split position isn't specified normally meaning to use what ever it is currently set to.
@@ -44,11 +54,6 @@
      */
     public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
 
-    /**
-     * Duration used for every split fade-in or fade-out.
-     */
-    public static final int FADE_DURATION = 133;
-
     @IntDef(prefix = {"SPLIT_POSITION_"}, value = {
             SPLIT_POSITION_UNDEFINED,
             SPLIT_POSITION_TOP_OR_LEFT,
@@ -57,38 +62,61 @@
     public @interface SplitPosition {
     }
 
-    /** The divider doesn't snap to any target and is freely placeable. */
-    public static final int SNAP_TO_NONE = 0;
-
-    /** A snap target positioned near the screen edge for a minimized task */
-    public static final int SNAP_TO_MINIMIZE = 1;
-
-    /** If the divider reaches this value, the left/top task should be dismissed. */
-    public static final int SNAP_TO_START_AND_DISMISS = 2;
-
     /** A snap target in the first half of the screen, where the split is roughly 30-70. */
-    public static final int SNAP_TO_30_70 = 3;
+    public static final int SNAP_TO_30_70 = 0;
 
     /** The 50-50 snap target */
-    public static final int SNAP_TO_50_50 = 4;
+    public static final int SNAP_TO_50_50 = 1;
 
     /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
-    public static final int SNAP_TO_70_30 = 5;
+    public static final int SNAP_TO_70_30 = 2;
+
+    /**
+     * These snap targets are used for split pairs in a stable, non-transient state. They may be
+     * persisted in Launcher when the user saves an app pair. They are a subset of
+     * {@link SnapPosition}.
+     */
+    @IntDef(prefix = { "SNAP_TO_" }, value = {
+            SNAP_TO_30_70,
+            SNAP_TO_50_50,
+            SNAP_TO_70_30
+    })
+    public @interface PersistentSnapPosition {}
+
+    /**
+     * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
+     */
+    public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
+        return snapPosition == SNAP_TO_30_70
+                || snapPosition == SNAP_TO_50_50
+                || snapPosition == SNAP_TO_70_30;
+    }
+
+    /** The divider doesn't snap to any target and is freely placeable. */
+    public static final int SNAP_TO_NONE = 10;
+
+    /** If the divider reaches this value, the left/top task should be dismissed. */
+    public static final int SNAP_TO_START_AND_DISMISS = 11;
 
     /** If the divider reaches this value, the right/bottom task should be dismissed. */
-    public static final int SNAP_TO_END_AND_DISMISS = 6;
+    public static final int SNAP_TO_END_AND_DISMISS = 12;
+
+    /** A snap target positioned near the screen edge for a minimized task */
+    public static final int SNAP_TO_MINIMIZE = 13;
 
     @IntDef(prefix = { "SNAP_TO_" }, value = {
-            SNAP_TO_NONE,
-            SNAP_TO_MINIMIZE,
-            SNAP_TO_START_AND_DISMISS,
             SNAP_TO_30_70,
             SNAP_TO_50_50,
             SNAP_TO_70_30,
-            SNAP_TO_END_AND_DISMISS
+            SNAP_TO_NONE,
+            SNAP_TO_START_AND_DISMISS,
+            SNAP_TO_END_AND_DISMISS,
+            SNAP_TO_MINIMIZE
     })
     public @interface SnapPosition {}
 
+    ///////////////
+
     public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
     public static final int[] CONTROLLED_WINDOWING_MODES =
             {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 9f9854e..11aa0546 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -199,6 +199,7 @@
             @ShellMainThread Handler mainHandler,
             @ShellMainThread Choreographer mainChoreographer,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
@@ -213,6 +214,7 @@
                     mainHandler,
                     mainChoreographer,
                     shellInit,
+                    shellCommandHandler,
                     taskOrganizer,
                     displayController,
                     shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index ccffa02..664d4491 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -23,6 +23,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -85,7 +86,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -601,7 +602,7 @@
 
     void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (options1 == null) options1 = new Bundle();
         final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
@@ -632,7 +633,7 @@
 
     void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
-            @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
             InstanceId instanceId) {
         if (options1 == null) options1 = new Bundle();
         final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
@@ -675,7 +676,7 @@
 
     private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         Intent fillInIntent = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
@@ -702,7 +703,7 @@
 
     private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
@@ -736,7 +737,8 @@
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
             PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
             @Nullable Bundle options2, @SplitPosition int splitPosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
@@ -767,7 +769,7 @@
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
             PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
             @Nullable Bundle options2, @SplitPosition int splitPosition,
-            @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
             InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
@@ -1225,7 +1227,7 @@
         @Override
         public void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
                 int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
-                @SnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+                @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
                 InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startTasks",
                     (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
@@ -1236,7 +1238,7 @@
         @Override
         public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
                 Bundle options1, int taskId, Bundle options2, int splitPosition,
-                @SnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+                @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
                 InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startIntentAndTaskWithLegacyTransition", (controller) ->
@@ -1248,7 +1250,7 @@
         @Override
         public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
                 @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-                @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+                @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
                 RemoteAnimationAdapter adapter, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController,
                     "startShortcutAndTaskWithLegacyTransition", (controller) ->
@@ -1260,8 +1262,8 @@
         @Override
         public void startTasks(int taskId1, @Nullable Bundle options1, int taskId2,
                 @Nullable Bundle options2, @SplitPosition int splitPosition,
-                @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
-                InstanceId instanceId) {
+                @PersistentSnapPosition int snapPosition,
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startTasks",
                     (controller) -> controller.mStageCoordinator.startTasks(taskId1, options1,
                             taskId2, options2, splitPosition, snapPosition, remoteTransition,
@@ -1271,7 +1273,7 @@
         @Override
         public void startIntentAndTask(PendingIntent pendingIntent, int userId1,
                 @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-                @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+                @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
                 @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
                     (controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1,
@@ -1282,8 +1284,8 @@
         @Override
         public void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
                 int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
-                @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
-                InstanceId instanceId) {
+                @PersistentSnapPosition int snapPosition,
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
                     (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
                             options2, splitPosition, snapPosition, remoteTransition, instanceId));
@@ -1294,7 +1296,7 @@
                 @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
                 PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
                 @Nullable Bundle options2, @SplitPosition int splitPosition,
-                @SnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+                @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
                 InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
                     (controller) ->
@@ -1309,8 +1311,8 @@
                 @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
                 PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
                 @Nullable Bundle options2, @SplitPosition int splitPosition,
-                @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
-                InstanceId instanceId) {
+                @PersistentSnapPosition int snapPosition,
+                @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntents",
                     (controller) ->
                             controller.startIntents(pendingIntent1, userId1, shortcutInfo1,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4ea14f4..5e2c61b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -128,7 +128,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.common.split.SplitWindowManager;
@@ -633,7 +633,7 @@
 
     /** Starts 2 tasks in one transition. */
     void startTasks(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId2 == INVALID_TASK_ID) {
@@ -661,7 +661,7 @@
     /** Start an intent and a task to a split pair in one transition. */
     void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId == INVALID_TASK_ID) {
@@ -683,7 +683,7 @@
     /** Starts a shortcut and a task to a split pair in one transition. */
     void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
-            @SnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
+            @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
             InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (taskId == INVALID_TASK_ID) {
@@ -710,7 +710,7 @@
      *                   {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
      */
     private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
-            @Nullable Bundle mainOptions, @SnapPosition int snapPosition,
+            @Nullable Bundle mainOptions, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         if (!mMainStage.isActive()) {
             // Build a request WCT that will launch both apps such that task 0 is on the main stage
@@ -744,7 +744,7 @@
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
             PendingIntent pendingIntent2, Intent fillInIntent2,
             @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (pendingIntent2 == null) {
@@ -796,7 +796,8 @@
     /** Starts a pair of tasks using legacy transition. */
     void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
             int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
         if (taskId2 == INVALID_TASK_ID) {
@@ -826,7 +827,7 @@
             @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
             @Nullable PendingIntent pendingIntent2, Intent fillInIntent2,
             @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
@@ -851,7 +852,7 @@
 
     void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
@@ -872,7 +873,7 @@
     /** Starts a pair of shortcut and task using legacy transition. */
     void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
-            @SplitPosition int splitPosition, @SnapPosition int snapPosition,
+            @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         if (options1 == null) options1 = new Bundle();
@@ -934,7 +935,7 @@
     private void startWithLegacyTransition(WindowContainerTransaction wct,
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
             @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
-            @SplitPosition int sidePosition, @SnapPosition int snapPosition,
+            @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
                 mainShortcutInfo, mainOptions, sidePosition, snapPosition, adapter, instanceId);
@@ -942,7 +943,7 @@
 
     private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
             @Nullable Bundle mainOptions, @SplitPosition int sidePosition,
-            @SnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+            @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
             InstanceId instanceId) {
         startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
                 null /* mainFillInIntent */, null /* mainShortcutInfo */, mainOptions, sidePosition,
@@ -957,7 +958,7 @@
     private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
             @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
             @Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle options,
-            @SplitPosition int sidePosition, @SnapPosition int snapPosition,
+            @SplitPosition int sidePosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
         if (!isSplitScreenVisible()) {
             exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index a68b41d..3e06d2d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -19,7 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Objects;
 
@@ -39,7 +39,7 @@
     public final float leftTaskPercent;
     public final float dividerWidthPercent;
     public final float dividerHeightPercent;
-    public final @SnapPosition int snapPosition;
+    public final @PersistentSnapPosition int snapPosition;
     /**
      * If {@code true}, that means at the time of creation of this object, the
      * split-screened apps were vertically stacked. This is useful in scenarios like
@@ -51,7 +51,7 @@
     public final int rightBottomTaskId;
 
     public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
-            int rightBottomTaskId, @SnapPosition int snapPosition) {
+            int rightBottomTaskId, @PersistentSnapPosition int snapPosition) {
         this.leftTopBounds = leftTopBounds;
         this.rightBottomBounds = rightBottomBounds;
         this.leftTopTaskId = leftTopTaskId;
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 afa2754..780bbb5 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
@@ -79,11 +79,13 @@
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
 
+import java.io.PrintWriter;
 import java.util.Optional;
 import java.util.function.Supplier;
 
@@ -97,6 +99,7 @@
 
     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
     private final ActivityTaskManager mActivityTaskManager;
+    private final ShellCommandHandler mShellCommandHandler;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellController mShellController;
     private final Context mContext;
@@ -134,6 +137,7 @@
             Handler mainHandler,
             Choreographer mainChoreographer,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
@@ -148,6 +152,7 @@
                 mainHandler,
                 mainChoreographer,
                 shellInit,
+                shellCommandHandler,
                 taskOrganizer,
                 displayController,
                 shellController,
@@ -167,6 +172,7 @@
             Handler mainHandler,
             Choreographer mainChoreographer,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             ShellController shellController,
@@ -189,7 +195,7 @@
         mTransitions = transitions;
         mDesktopTasksController = desktopTasksController;
         mRecentsTransitionHandler = recentsTransitionHandler;
-
+        mShellCommandHandler = shellCommandHandler;
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
         mTransactionFactory = transactionFactory;
@@ -206,6 +212,7 @@
                 onRecentsTransitionStarted(transition);
             }
         });
+        mShellCommandHandler.addDumpCallback(this::dump, this);
     }
 
     @Override
@@ -593,6 +600,15 @@
             super.dispose();
         }
 
+        @Override
+        public String toString() {
+            return "EventReceiver"
+                    + "{"
+                    + "tasksOnDisplay="
+                    + mTasksOnDisplay
+                    + "}";
+        }
+
         private void incrementTaskNumber() {
             mTasksOnDisplay++;
         }
@@ -981,6 +997,15 @@
                 && mSplitScreenController.isTaskInSplitScreen(taskId);
     }
 
+    private void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = prefix + "  ";
+        pw.println(prefix + "DesktopModeWindowDecorViewModel");
+        pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled());
+        pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
+        pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
+        pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
+    }
+
     private class DragStartListenerImpl
             implements DragPositioningCallbackUtility.DragStartListener {
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 84ec6b3..380b59e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.windowingModeToString;
 
 import android.app.ActivityManager;
 import android.app.WindowConfiguration.WindowingMode;
@@ -661,6 +662,17 @@
         mRelayoutBlock++;
     }
 
+    @Override
+    public String toString() {
+        return "{"
+                + "mPositionInParent=" + mPositionInParent + ", "
+                + "mRelayoutBlock=" + mRelayoutBlock + ", "
+                + "taskId=" + mTaskInfo.taskId + ", "
+                + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
+                + "isFocused=" + isFocused()
+                + "}";
+    }
+
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
new file mode 100644
index 0000000..fe26110
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.split
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenConstantsTest {
+
+    /**
+     * Ensures that some important constants are not changed from their set values. These values
+     * are persisted in user-defined app pairs, and changing them will break things.
+     */
+    @Test
+    fun shouldKeepExistingConstantValues() {
+        assertEquals(
+            "the value of SPLIT_POSITION_TOP_OR_LEFT should be 0",
+            0,
+            SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT,
+        )
+        assertEquals(
+            "the value of SPLIT_POSITION_BOTTOM_OR_RIGHT should be 1",
+            1,
+            SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
+        )
+        assertEquals(
+            "the value of SNAP_TO_30_70 should be 0",
+            0,
+            SplitScreenConstants.SNAP_TO_30_70,
+        )
+        assertEquals(
+            "the value of SNAP_TO_50_50 should be 1",
+            1,
+            SplitScreenConstants.SNAP_TO_50_50,
+        )
+        assertEquals(
+            "the value of SNAP_TO_70_30 should be 2",
+            2,
+            SplitScreenConstants.SNAP_TO_70_30,
+        )
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 00d70a7..8eaf5a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -49,6 +49,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.sysui.KeyguardChangeListener
+import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -89,6 +90,7 @@
     @Mock private lateinit var mockShellExecutor: ShellExecutor
     @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
     @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
+    @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
@@ -105,6 +107,7 @@
                 mockMainHandler,
                 mockMainChoreographer,
                 shellInit,
+                mockShellCommandHandler,
                 mockTaskOrganizer,
                 mockDisplayController,
                 mockShellController,
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 2c13ceb..a952be0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -65,21 +65,41 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
 }
 
-static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
-                                      jint tileModeX, jint tileModeY, bool isDirectSampled,
-                                      const SkSamplingOptions& sampling) {
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkGainmapInfo sNoOpGainmap = {
+        .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
+        .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
+        .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
+        .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
+        .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
+        .fDisplayRatioSdr = 1.f,
+        .fDisplayRatioHdr = 1.f,
+};
+
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+                                      jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
+                                      bool isDirectSampled, jlong overrideGainmapPtr) {
+    SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
+                                              : SkSamplingOptions(filter ? SkFilterMode::kLinear
+                                                                         : SkFilterMode::kNearest,
+                                                                  SkMipmapMode::kNone);
     const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
     sk_sp<SkImage> image;
     if (bitmapHandle) {
         // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
         // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
         auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
         image = bitmap.makeImage();
+        if (!gainmap && bitmap.hasGainmap()) {
+            gainmap = bitmap.gainmap().get();
+        }
 
-        if (!isDirectSampled && bitmap.hasGainmap()) {
-            sk_sp<SkShader> gainmapShader = MakeGainmapShader(
-                    image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
-                    (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+        if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
+            sk_sp<SkShader> gainmapShader =
+                    MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
+                                      (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
             if (gainmapShader) {
                 if (matrix) {
                     gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
@@ -111,26 +131,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
-                                      jint tileModeX, jint tileModeY, bool filter,
-                                      bool isDirectSampled) {
-    SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
-                               SkMipmapMode::kNone);
-    return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
-                                    isDirectSampled, sampling);
-}
-
-static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
-                                                  jlong bitmapHandle, jint tileModeX,
-                                                  jint tileModeY, jint maxAniso,
-                                                  bool isDirectSampled) {
-    auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
-    return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
-                                    isDirectSampled, sampling);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
 static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
     const size_t count = env->GetArrayLength(colorArray);
     const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -419,8 +419,7 @@
 };
 
 static const JNINativeMethod gBitmapShaderMethods[] = {
-        {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
-        {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+        {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
 
 };
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
index 8986e52..98ad22c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/IntentParser.kt
@@ -29,7 +29,7 @@
     packageManager: PackageManager,
     previousIntent: Intent? = null,
 ): Request {
-    this.toRequestClose(packageManager, previousIntent)?.let { closeRequest ->
+    this.toRequestClose(previousIntent)?.let { closeRequest ->
         return closeRequest
     }
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt
new file mode 100644
index 0000000..3471070
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/PasswordKtx.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0N
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ktx
+
+import androidx.activity.result.IntentSenderRequest
+import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
+import com.android.credentialmanager.model.Password
+
+fun Password.getIntentSenderRequest(
+    isAutoSelected: Boolean = false
+): IntentSenderRequest {
+    val entryIntent = entry.frameworkExtrasIntent
+    entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, isAutoSelected)
+
+    return IntentSenderRequest.Builder(
+        pendingIntent = passwordCredentialEntry.pendingIntent
+    ).setFillInIntent(entryIntent).build()
+}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
index 555a86f..99dc9ec 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCancelMapper.kt
@@ -31,9 +31,6 @@
             Log.d(TAG, "Received UI cancel request with an invalid package name.")
             null
         } else {
-            Request.Cancel(
-                showCancellationUi = cancelUiRequest.shouldShowCancellationUi(),
-                appName = appLabel
-            )
+            Request.Cancel(appName = appLabel)
         }
     }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
index 6de3e7d..02ee77b 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestCloseMapper.kt
@@ -17,31 +17,33 @@
 package com.android.credentialmanager.mapper
 
 import android.content.Intent
-import android.content.pm.PackageManager
+import com.android.credentialmanager.ktx.cancelUiRequest
 import com.android.credentialmanager.ktx.requestInfo
 import com.android.credentialmanager.model.Request
 
 fun Intent.toRequestClose(
-    packageManager: PackageManager,
     previousIntent: Intent? = null,
 ): Request.Close? {
     // Close request comes as "Cancel" request from Credential Manager API
-    val currentRequest = toRequestCancel(packageManager = packageManager) ?: return null
+    this.cancelUiRequest?.let { cancelUiRequest ->
 
-    if (currentRequest.showCancellationUi) {
-        // Current request is to Cancel and not to Close
-        return null
-    }
-
-    previousIntent?.let {
-        val previousToken = previousIntent.requestInfo?.token
-        val currentToken = this.requestInfo?.token
-
-        if (previousToken != currentToken) {
-            // Current cancellation is for a different request, don't close the current flow.
+        if (cancelUiRequest.shouldShowCancellationUi()) {
+            // Current request is to Cancel and not to Close
             return null
         }
+
+        previousIntent?.let {
+            val previousToken = previousIntent.requestInfo?.token
+            val currentToken = this.requestInfo?.token
+
+            if (previousToken != currentToken) {
+                // Current cancellation is for a different request, don't close the current flow.
+                return null
+            }
+        }
+
+        return Request.Close
     }
 
-    return Request.Close
+    return null
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index 6011a1c..ed98f3e 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -37,7 +37,6 @@
      * Request to close the app, displaying a message to the user.
      */
     data class Cancel(
-        val showCancellationUi: Boolean,
         val appName: String
     ) : Request()
 
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
new file mode 100644
index 0000000..1cce3ba
--- /dev/null
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/repository/PasswordRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0N
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.repository
+
+import android.content.Intent
+import android.credentials.ui.BaseDialogResult
+import android.credentials.ui.ProviderPendingIntentResponse
+import android.credentials.ui.UserSelectionDialogResult
+import android.os.Bundle
+import android.util.Log
+import com.android.credentialmanager.TAG
+import com.android.credentialmanager.model.Password
+import com.android.credentialmanager.model.Request
+
+class PasswordRepository {
+
+    suspend fun selectPassword(
+        password: Password,
+        request: Request.Get,
+        resultCode: Int? = null,
+        resultData: Intent? = null,
+    ) {
+        Log.d(TAG, "password selected: {provider=${password.providerId}" +
+            ", key=${password.entry.key}, subkey=${password.entry.subkey}}")
+
+        val userSelectionDialogResult = UserSelectionDialogResult(
+            request.token,
+            password.providerId,
+            password.entry.key,
+            password.entry.subkey,
+            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
+        )
+        val resultDataBundle = Bundle()
+        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
+        request.resultReceiver?.send(
+            BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+            resultDataBundle
+        )
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
index 7c81fd0..e8e4033 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -18,11 +18,13 @@
 
 import android.app.Application
 import com.android.credentialmanager.di.inject
+import com.android.credentialmanager.repository.PasswordRepository
 import com.android.credentialmanager.repository.RequestRepository
 
 class CredentialSelectorApp : Application() {
 
     lateinit var requestRepository: RequestRepository
+    lateinit var passwordRepository: PasswordRepository
 
     override fun onCreate() {
         super.onCreate()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
index a11017b..1e8f83d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/di/DI.kt
@@ -2,12 +2,14 @@
 
 import android.app.Application
 import com.android.credentialmanager.CredentialSelectorApp
+import com.android.credentialmanager.repository.PasswordRepository
 import com.android.credentialmanager.repository.RequestRepository
 
 // TODO b/301601582 add Hilt for dependency injection
 
 fun CredentialSelectorApp.inject() {
     requestRepository = requestRepository(application = this)
+    passwordRepository = passwordRepository()
 }
 
 private fun requestRepository(
@@ -15,3 +17,5 @@
 ): RequestRepository = RequestRepository(
     application = application,
 )
+
+private fun passwordRepository(): PasswordRepository = PasswordRepository()
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index c885ec4..c87cfd3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -35,6 +35,7 @@
 import com.android.credentialmanager.ui.components.DialogButtonsRow
 import com.android.credentialmanager.ui.components.PasswordRow
 import com.android.credentialmanager.ui.components.SignInHeader
+import com.android.credentialmanager.ui.model.PasswordUiModel
 import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
 import com.google.android.horologist.annotations.ExperimentalHorologistApi
 import com.google.android.horologist.compose.layout.ScalingLazyColumnState
@@ -59,9 +60,8 @@
         }
 
         is SinglePasswordScreenUiState.Loaded -> {
-            val model = state.passwordUiModel
             SinglePasswordScreen(
-                email = model.email,
+                passwordUiModel = state.passwordUiModel,
                 onCancelClick = viewModel::onCancelClick,
                 onOKClick = viewModel::onOKClick,
                 columnState = columnState,
@@ -98,7 +98,7 @@
 
 @Composable
 fun SinglePasswordScreen(
-    email: String,
+    passwordUiModel: PasswordUiModel,
     onCancelClick: () -> Unit,
     onOKClick: () -> Unit,
     columnState: ScalingLazyColumnState,
@@ -113,7 +113,7 @@
         },
         accountContent = {
             PasswordRow(
-                email = email,
+                email = passwordUiModel.email,
                 modifier = Modifier.padding(top = 10.dp),
             )
         },
@@ -134,7 +134,7 @@
 @Composable
 fun SinglePasswordScreenPreview() {
     SinglePasswordScreen(
-        email = "beckett_bakery@gmail.com",
+        passwordUiModel = PasswordUiModel(email = "beckett_bakery@gmail.com"),
         onCancelClick = {},
         onOKClick = {},
         columnState = belowTimeTextPreview(),
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 9b06622..3167e67 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -17,10 +17,6 @@
 package com.android.credentialmanager.ui.screens.single.password
 
 import android.content.Intent
-import android.credentials.ui.BaseDialogResult
-import android.credentials.ui.ProviderPendingIntentResponse
-import android.credentials.ui.UserSelectionDialogResult
-import android.os.Bundle
 import android.util.Log
 import androidx.activity.result.IntentSenderRequest
 import androidx.annotation.MainThread
@@ -30,10 +26,11 @@
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewmodel.CreationExtras
 import com.android.credentialmanager.CredentialSelectorApp
-import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
 import com.android.credentialmanager.TAG
+import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Password
 import com.android.credentialmanager.model.Request
+import com.android.credentialmanager.repository.PasswordRepository
 import com.android.credentialmanager.repository.RequestRepository
 import com.android.credentialmanager.ui.model.PasswordUiModel
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,6 +40,7 @@
 
 class SinglePasswordScreenViewModel(
     private val requestRepository: RequestRepository,
+    private val passwordRepository: PasswordRepository,
 ) : ViewModel() {
 
     private var initializeCalled = false
@@ -87,15 +85,8 @@
     }
 
     fun onOKClick() {
-        // TODO: b/301206470 move this code to shared module
-        val entryIntent = password.entry.frameworkExtrasIntent
-        entryIntent?.putExtra(IS_AUTO_SELECTED_KEY, false)
-        val intentSenderRequest = IntentSenderRequest.Builder(
-            pendingIntent = password.passwordCredentialEntry.pendingIntent
-        ).setFillInIntent(entryIntent).build()
-
         _uiState.value = SinglePasswordScreenUiState.PasswordSelected(
-            intentSenderRequest = intentSenderRequest
+            intentSenderRequest = password.getIntentSenderRequest()
         )
     }
 
@@ -103,25 +94,16 @@
         resultCode: Int? = null,
         resultData: Intent? = null,
     ) {
-        // TODO: b/301206470 move this code to shared module
-        Log.d(TAG, "credential selected: {provider=${password.providerId}" +
-            ", key=${password.entry.key}, subkey=${password.entry.subkey}}")
+        viewModelScope.launch {
+            passwordRepository.selectPassword(
+                password = password,
+                request = requestGet,
+                resultCode = resultCode,
+                resultData = resultData
+            )
 
-        val userSelectionDialogResult = UserSelectionDialogResult(
-            requestGet.token,
-            password.providerId,
-            password.entry.key,
-            password.entry.subkey,
-            if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
-        )
-        val resultDataBundle = Bundle()
-        UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
-        requestGet.resultReceiver?.send(
-            BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
-            resultDataBundle
-        )
-
-        _uiState.value = SinglePasswordScreenUiState.Completed
+            _uiState.value = SinglePasswordScreenUiState.Completed
+        }
     }
 
     companion object {
@@ -135,6 +117,7 @@
 
                 return SinglePasswordScreenViewModel(
                     requestRepository = (application as CredentialSelectorApp).requestRepository,
+                    passwordRepository = application.passwordRepository,
                 ) as T
             }
         }
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 70832f5..437f8af 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -5,4 +5,11 @@
     namespace: "systemui"
     description: "An Example Flag"
     bug: "292511372"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "sysui_teamfood"
+    namespace: "systemui"
+    description: "Enables all the sysui classic flags that are marked as being in teamfood"
+    bug: "302578396"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index 44c4105..bb2fbf7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -26,6 +26,7 @@
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.theme.LocalAndroidColorScheme
 
@@ -34,11 +35,13 @@
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
+    colors: ButtonColors = filledButtonColors(),
+    verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
     content: @Composable RowScope.() -> Unit,
 ) {
     androidx.compose.material3.Button(
-        modifier = modifier.padding(vertical = 6.dp).height(36.dp),
-        colors = filledButtonColors(),
+        modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
+        colors = colors,
         contentPadding = ButtonPaddings,
         onClick = onClick,
         enabled = enabled,
@@ -52,13 +55,16 @@
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
+    colors: ButtonColors = outlineButtonColors(),
+    border: BorderStroke? = outlineButtonBorder(),
+    verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
     content: @Composable RowScope.() -> Unit,
 ) {
     androidx.compose.material3.OutlinedButton(
-        modifier = modifier.padding(vertical = 6.dp).height(36.dp),
+        modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
         enabled = enabled,
-        colors = outlineButtonColors(),
-        border = outlineButtonBorder(),
+        colors = colors,
+        border = border,
         contentPadding = ButtonPaddings,
         onClick = onClick,
     ) {
@@ -71,6 +77,7 @@
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
     enabled: Boolean = true,
+    colors: ButtonColors = textButtonColors(),
     content: @Composable RowScope.() -> Unit,
 ) {
     androidx.compose.material3.TextButton(
@@ -78,10 +85,11 @@
         modifier = modifier,
         enabled = enabled,
         content = content,
-        colors = textButtonColors(),
+        colors = colors,
     )
 }
 
+private val DefaultPlatformButtonVerticalPadding = 6.dp
 private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
 
 @Composable
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 a61e959..a9944f7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,18 +24,30 @@
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
@@ -48,12 +60,18 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.DpOffset
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.times
+import com.android.compose.PlatformButton
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
@@ -62,6 +80,8 @@
 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.common.shared.model.Text.Companion.loadText
+import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Direction
@@ -70,6 +90,9 @@
 import com.android.systemui.scene.shared.model.UserAction
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.pow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -164,7 +187,7 @@
     Column(
         horizontalAlignment = Alignment.CenterHorizontally,
         verticalArrangement = Arrangement.spacedBy(60.dp),
-        modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
+        modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 92.dp)
     ) {
         Crossfade(
             targetState = message,
@@ -201,18 +224,20 @@
             }
         }
 
-        Button(
-            onClick = viewModel::onEmergencyServicesButtonClicked,
-            colors =
-                ButtonDefaults.buttonColors(
-                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                    contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                ),
-        ) {
-            Text(
-                text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
-                style = MaterialTheme.typography.bodyMedium,
-            )
+        if (viewModel.isEmergencyButtonVisible) {
+            Button(
+                onClick = viewModel::onEmergencyServicesButtonClicked,
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+            }
         }
 
         if (dialogMessage != null) {
@@ -241,16 +266,133 @@
 /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
 @Composable
 private fun UserSwitcher(
+    viewModel: BouncerViewModel,
     modifier: Modifier = Modifier,
 ) {
-    Box(modifier) {
-        Text(
-            text = "TODO: the user switcher goes here",
-            modifier = Modifier.align(Alignment.Center)
+    val selectedUserImage by viewModel.selectedUserImage.collectAsState(null)
+    val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList())
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center,
+        modifier = modifier,
+    ) {
+        selectedUserImage?.let {
+            Image(
+                bitmap = it.asImageBitmap(),
+                contentDescription = null,
+                modifier = Modifier.size(SelectedUserImageSize),
+            )
+        }
+
+        UserSwitcherDropdown(
+            items = dropdownItems,
         )
     }
 }
 
+@Composable
+private fun UserSwitcherDropdown(
+    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+) {
+    val (isDropdownExpanded, setDropdownExpanded) = remember { mutableStateOf(false) }
+
+    items.firstOrNull()?.let { firstDropdownItem ->
+        Spacer(modifier = Modifier.height(40.dp))
+
+        Box {
+            PlatformButton(
+                modifier =
+                    Modifier
+                        // Remove the built-in padding applied inside PlatformButton:
+                        .padding(vertical = 0.dp)
+                        .width(UserSwitcherDropdownWidth)
+                        .height(UserSwitcherDropdownHeight),
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+                        contentColor = MaterialTheme.colorScheme.onSurface,
+                    ),
+                onClick = { setDropdownExpanded(!isDropdownExpanded) },
+            ) {
+                val context = LocalContext.current
+                Text(
+                    text = checkNotNull(firstDropdownItem.text.loadText(context)),
+                    style = MaterialTheme.typography.headlineSmall,
+                    maxLines = 1,
+                    overflow = TextOverflow.Ellipsis,
+                )
+
+                Spacer(modifier = Modifier.weight(1f))
+
+                Icon(
+                    imageVector = Icons.Default.KeyboardArrowDown,
+                    contentDescription = null,
+                    modifier = Modifier.size(32.dp),
+                )
+            }
+
+            UserSwitcherDropdownMenu(
+                isExpanded = isDropdownExpanded,
+                items = items,
+                onDismissed = { setDropdownExpanded(false) },
+            )
+        }
+    }
+}
+
+@Composable
+private fun UserSwitcherDropdownMenu(
+    isExpanded: Boolean,
+    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+    onDismissed: () -> Unit,
+) {
+    val context = LocalContext.current
+
+    // TODO(b/303071855): once the FR is fixed, remove this composition local override.
+    MaterialTheme(
+        colorScheme =
+            MaterialTheme.colorScheme.copy(
+                surface = MaterialTheme.colorScheme.surfaceContainerHighest,
+            ),
+        shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)),
+    ) {
+        DropdownMenu(
+            expanded = isExpanded,
+            onDismissRequest = onDismissed,
+            offset =
+                DpOffset(
+                    x = 0.dp,
+                    y = -UserSwitcherDropdownHeight,
+                ),
+            modifier = Modifier.width(UserSwitcherDropdownWidth),
+        ) {
+            items.forEach { userSwitcherDropdownItem ->
+                DropdownMenuItem(
+                    leadingIcon = {
+                        Icon(
+                            icon = userSwitcherDropdownItem.icon,
+                            tint = MaterialTheme.colorScheme.primary,
+                            modifier = Modifier.size(28.dp),
+                        )
+                    },
+                    text = {
+                        Text(
+                            text = checkNotNull(userSwitcherDropdownItem.text.loadText(context)),
+                            style = MaterialTheme.typography.bodyLarge,
+                            color = MaterialTheme.colorScheme.onSurface,
+                        )
+                    },
+                    onClick = {
+                        onDismissed()
+                        userSwitcherDropdownItem.onClick()
+                    },
+                )
+            }
+        }
+    }
+}
+
 /**
  * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap
  * anywhere on the background to flip their positions.
@@ -293,7 +435,7 @@
                         1f
                     } else {
                         // Since the user switcher is not first, the elements have to be swapped
-                        // horizontally. In the case of RTL locales, this means pushing the user
+                        // horizontally. In the case of RTL locale, this means pushing the user
                         // switcher to the left, hence the negative number.
                         -1f
                     },
@@ -301,21 +443,28 @@
             )
 
         UserSwitcher(
+            viewModel = viewModel,
             modifier =
                 Modifier.fillMaxHeight().weight(1f).graphicsLayer {
                     translationX = size.width * animatedOffset
+                    alpha = animatedAlpha(animatedOffset)
                 },
         )
-        Bouncer(
-            viewModel = viewModel,
-            dialogFactory = dialogFactory,
+        Box(
             modifier =
                 Modifier.fillMaxHeight().weight(1f).graphicsLayer {
                     // A negative sign is used to make sure this is offset in the direction that's
                     // opposite of the direction that the user switcher is pushed in.
                     translationX = -size.width * animatedOffset
-                },
-        )
+                    alpha = animatedAlpha(animatedOffset)
+                }
+        ) {
+            Bouncer(
+                viewModel = viewModel,
+                dialogFactory = dialogFactory,
+                modifier = Modifier.widthIn(max = 400.dp).align(Alignment.BottomCenter),
+            )
+        }
     }
 }
 
@@ -330,6 +479,7 @@
         modifier = modifier,
     ) {
         UserSwitcher(
+            viewModel = viewModel,
             modifier = Modifier.fillMaxWidth().weight(1f),
         )
         Bouncer(
@@ -343,3 +493,36 @@
 interface BouncerSceneDialogFactory {
     operator fun invoke(): AlertDialog
 }
+
+/**
+ * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
+ * the two reaches a stopping point but `0` in the middle of the transition.
+ */
+private fun animatedAlpha(
+    offset: Float,
+): Float {
+    // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around
+    // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs
+    // between x = 0 and x = 1.
+    //
+    // The minimum values of the curves are at -0.5 and +0.5.
+    //
+    // Both U curves are vertically scaled such that they reach the points (-1, 1) and (1, 1).
+    //
+    // Breaking it down, it's y = a×(|x|-m)²+b, where:
+    // x: the offset
+    // y: the alpha
+    // m: x-axis center of the parabolic curves, where the minima are.
+    // b: y-axis offset to apply to the entire curve so the animation spends more time with alpha =
+    // 0.
+    // a: amplitude to scale the parabolic curves to reach y = 1 at x = -1, x = 0, and x = +1.
+    val m = 0.5f
+    val b = -0.25
+    val a = (1 - b) / m.pow(2)
+
+    return max(0f, (a * (abs(offset) - m).pow(2) + b).toFloat())
+}
+
+private val SelectedUserImageSize = 190.dp
+private val UserSwitcherDropdownWidth = SelectedUserImageSize + 2 * 29.dp
+private val UserSwitcherDropdownHeight = 60.dp
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cb76ad7..651594c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -509,7 +509,6 @@
 
     private var isQueued = AtomicBoolean(false)
     fun verifyLoadedProviders() {
-        Log.i(TAG, Thread.currentThread().getStackTrace().toString())
         val shouldSchedule = isQueued.compareAndSet(false, true)
         if (!shouldSchedule) {
             logger.tryLog(
diff --git a/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml
new file mode 100644
index 0000000..d4916b4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="@android:color/white"/>
+            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml
new file mode 100644
index 0000000..7bc120e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bluetooth_tile_dialog_bg_off_busy.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background">
+        <shape>
+            <solid android:color="?android:attr/colorControlHighlight" />
+            <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 6dd44fb..1c7e997 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -14,81 +14,74 @@
   ~ limitations under the License.
   -->
 
-<!-- TODO(b/298124674) remove this root -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/bluetooth_device_list_container"
+    android:id="@+id/bluetooth_device_row"
+    style="@style/BluetoothTileDialog.Device"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
+    android:layout_height="@dimen/bluetooth_dialog_device_height"
+    android:paddingEnd="24dp"
+    android:paddingStart="20dp"
     android:layout_marginBottom="4dp">
 
-    <androidx.constraintlayout.widget.ConstraintLayout
-        android:id="@+id/bluetooth_device_row"
-        style="@style/BluetoothTileDialog.Device"
-        android:layout_height="@dimen/bluetooth_dialog_device_height"
-        android:paddingEnd="24dp"
+    <ImageView
+        android:id="@+id/bluetooth_device_icon"
+        android:contentDescription="@string/accessibility_bluetooth_device_icon"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_gravity="center_vertical" />
+
+    <TextView
+        android:layout_width="0dp"
+        android:id="@+id/bluetooth_device_name"
+        style="@style/BluetoothTileDialog.DeviceName"
         android:paddingStart="20dp"
-        android:baselineAligned="false">
+        android:paddingTop="10dp"
+        app:layout_constraintWidth_percent="0.7"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
+        app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+        app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary"
+        android:gravity="center_vertical"
+        android:textSize="14sp" />
 
-        <ImageView
-            android:id="@+id/bluetooth_device_icon"
-            android:contentDescription="@string/accessibility_bluetooth_device_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            android:layout_gravity="center_vertical" />
+    <TextView
+        android:layout_width="0dp"
+        android:id="@+id/bluetooth_device_summary"
+        style="@style/BluetoothTileDialog.DeviceSummary"
+        android:paddingStart="20dp"
+        android:paddingBottom="10dp"
+        app:layout_constraintWidth_percent="0.7"
+        app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
+        app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
+        app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:gravity="center_vertical" />
 
-        <View
-            android:id="@+id/bluetooth_device"
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            app:layout_constraintTop_toTopOf="@+id/bluetooth_device_name"
-            app:layout_constraintBottom_toBottomOf="@+id/bluetooth_device_summary"
-            app:layout_constraintStart_toStartOf="@+id/bluetooth_device_name"
-            app:layout_constraintEnd_toEndOf="@+id/bluetooth_device_name" />
+    <View
+        android:id="@+id/gear_icon"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
+        app:layout_constraintEnd_toEndOf="@+id/gear_icon_image"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
 
-        <TextView
-            android:layout_width="0dp"
-            android:id="@+id/bluetooth_device_name"
-            style="@style/BluetoothTileDialog.DeviceName"
-            android:paddingStart="20dp"
-            android:paddingTop="10dp"
-            app:layout_constraintWidth_percent="0.7"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
-            app:layout_constraintEnd_toStartOf="@+id/gear_icon"
-            app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary"
-            android:gravity="center_vertical"
-            android:textSize="14sp" />
-
-        <TextView
-            android:layout_width="0dp"
-            android:id="@+id/bluetooth_device_summary"
-            style="@style/BluetoothTileDialog.DeviceSummary"
-            android:paddingStart="20dp"
-            android:paddingBottom="10dp"
-            app:layout_constraintWidth_percent="0.7"
-            app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
-            app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
-            app:layout_constraintEnd_toStartOf="@+id/gear_icon"
-            app:layout_constraintBottom_toBottomOf="parent"
-            android:gravity="center_vertical" />
-
-        <ImageView
-            android:id="@+id/gear_icon"
-            android:src="@drawable/ic_settings_24dp"
-            android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
-            android:layout_width="0dp"
-            android:layout_height="24dp"
-            app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintWidth_percent="0.3"
-            android:gravity="center_vertical"
-            android:paddingStart="10dp" />
-    </androidx.constraintlayout.widget.ConstraintLayout>
-</LinearLayout>
\ No newline at end of file
+    <ImageView
+        android:id="@+id/gear_icon_image"
+        android:src="@drawable/ic_settings_24dp"
+        android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
+        android:layout_width="0dp"
+        android:layout_height="24dp"
+        app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintWidth_percent="0.3"
+        android:gravity="center_vertical"
+        android:paddingStart="10dp" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6cdd15e..81101d8 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -221,6 +221,7 @@
     <item type="id" name="split_shade_guideline" />
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
+    <item type="id" name="burn_in_layer" />
 
     <!-- Privacy dialog -->
     <item type="id" name="privacy_dialog_close_app_button" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 4b23795..67b7052 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -552,6 +552,10 @@
         constraintSet.applyTo(layout);
     }
 
+    public ClockController getClockController() {
+        return mKeyguardClockSwitchController.getClock();
+    }
+
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         mView.dump(pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index c09e68d..f94f8c5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -328,7 +328,10 @@
     private val udfpsControllerCallback =
         object : UdfpsController.Callback {
             override fun onFingerDown() {
-                showDwellRipple()
+                // only show dwell ripple for device entry
+                if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+                    showDwellRipple()
+                }
             }
 
             override fun onFingerUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index 0cbfb68..7f3b794 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -16,10 +16,16 @@
 
 package com.android.systemui.bouncer.ui
 
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
 import dagger.Binds
 import dagger.Module
 
-@Module
+@Module(
+    includes =
+        [
+            BouncerViewModelModule::class,
+        ],
+)
 interface BouncerViewModule {
     /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
     @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 66c6162..55bc653 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.annotation.StringRes
-import android.util.Log
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -103,11 +102,12 @@
      *
      * @see BouncerInteractor.authenticate
      */
-    protected fun tryAuthenticate(useAutoConfirm: Boolean = false) {
+    protected fun tryAuthenticate(
+        input: List<Any> = getInput(),
+        useAutoConfirm: Boolean = false,
+    ) {
         viewModelScope.launch {
-            Log.d("Danny", "tryAuthenticate(useAutoConfirm=$useAutoConfirm)")
-            val authenticationResult = interactor.authenticate(getInput(), useAutoConfirm)
-            Log.d("Danny", "result = $authenticationResult")
+            val authenticationResult = interactor.authenticate(input, useAutoConfirm)
             if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
                 return@launch
             }
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 c98cf31..2cb98d8 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
@@ -17,19 +17,29 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
+import android.graphics.Bitmap
+import androidx.core.graphics.drawable.toBitmap
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import javax.inject.Inject
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.ui.viewmodel.UserActionViewModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import com.android.systemui.user.ui.viewmodel.UserViewModel
+import dagger.Module
+import dagger.Provides
 import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -42,17 +52,53 @@
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input on bouncer UIs. */
-@SysUISingleton
-class BouncerViewModel
-@Inject
-constructor(
+class BouncerViewModel(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val bouncerInteractor: BouncerInteractor,
     authenticationInteractor: AuthenticationInteractor,
     flags: SceneContainerFlags,
+    private val telephonyInteractor: TelephonyInteractor,
+    selectedUser: Flow<UserViewModel>,
+    users: Flow<List<UserViewModel>>,
+    userSwitcherMenu: Flow<List<UserActionViewModel>>,
 ) {
+    val selectedUserImage: StateFlow<Bitmap?> =
+        selectedUser
+            .map { it.image.toBitmap() }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
+        combine(
+                users,
+                userSwitcherMenu,
+            ) { users, actions ->
+                users.map { user ->
+                    UserSwitcherDropdownItemViewModel(
+                        icon = Icon.Loaded(user.image, contentDescription = null),
+                        text = user.name,
+                        onClick = user.onClicked ?: {},
+                    )
+                } +
+                    actions.map { action ->
+                        UserSwitcherDropdownItemViewModel(
+                            icon = Icon.Resource(action.iconResourceId, contentDescription = null),
+                            text = Text.Resource(action.textResourceId),
+                            onClick = action.onClicked,
+                        )
+                    }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = emptyList(),
+            )
+
     private val isInputEnabled: StateFlow<Boolean> =
         bouncerInteractor.isThrottled
             .map { !it }
@@ -102,6 +148,9 @@
                     ),
             )
 
+    val isEmergencyButtonVisible: Boolean
+        get() = telephonyInteractor.hasTelephonyRadio
+
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
@@ -200,4 +249,40 @@
          */
         val isUpdateAnimated: Boolean,
     )
+
+    data class UserSwitcherDropdownItemViewModel(
+        val icon: Icon,
+        val text: Text,
+        val onClick: () -> Unit,
+    )
+}
+
+@Module
+object BouncerViewModelModule {
+
+    @Provides
+    @SysUISingleton
+    fun viewModel(
+        @Application applicationContext: Context,
+        @Application applicationScope: CoroutineScope,
+        @Main mainDispatcher: CoroutineDispatcher,
+        bouncerInteractor: BouncerInteractor,
+        authenticationInteractor: AuthenticationInteractor,
+        flags: SceneContainerFlags,
+        telephonyInteractor: TelephonyInteractor,
+        userSwitcherViewModel: UserSwitcherViewModel,
+    ): BouncerViewModel {
+        return BouncerViewModel(
+            applicationContext = applicationContext,
+            applicationScope = applicationScope,
+            mainDispatcher = mainDispatcher,
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+            flags = flags,
+            telephonyInteractor = telephonyInteractor,
+            selectedUser = userSwitcherViewModel.selectedUser,
+            users = userSwitcherViewModel.users,
+            userSwitcherMenu = userSwitcherViewModel.menu,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 52adf54..d301085 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -166,7 +166,8 @@
             interactor.onFalseUserInput()
         }
 
-        tryAuthenticate()
+        clearInput()
+        tryAuthenticate(input = pattern)
     }
 
     override fun clearInput() {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index ea3ddac..f6e0296 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -17,5 +17,5 @@
 constructor(
     featureFlags: FeatureFlagsClassic,
 ) : CommunalRepository {
-    override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_HUB)
+    override val isCommunalEnabled = featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
index 1057852..44b5e99 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelperWrapper.kt
@@ -28,4 +28,8 @@
     fun burnInProgressOffset(): Float {
         return getBurnInProgressOffset()
     }
+
+    fun burnInScale(): Float {
+        return getBurnInScale()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c79952e..8587329 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -817,7 +817,13 @@
     val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog", teamfood = true)
 
     // TODO(b/300995746): Tracking Bug
-    /** Enable communal hub features. */
+    /** A resource flag for whether the communal service is enabled. */
     @JvmField
-    val COMMUNAL_HUB = resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_hub")
+    val COMMUNAL_SERVICE_ENABLED = resourceBooleanFlag(R.bool.config_communalServiceEnabled,
+        "communal_service_enabled")
+
+    // TODO(b/303131306): Tracking Bug
+    /** Whether communal hub features are enabled. */
+    @JvmField
+    val COMMUNAL_HUB = unreleasedFlag("communal_hub")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 2a69ec5..fde92b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -413,7 +413,7 @@
     fun canPerformInWindowLauncherAnimations(): Boolean {
         // TODO(b/278086361): Refactor in-window animations.
         return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
-                isNexusLauncherUnderneath() &&
+                isSupportedLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
                 !notificationShadeWindowController.isLaunchingActivity &&
@@ -427,7 +427,7 @@
      */
     private fun logInWindowAnimationConditions() {
         Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
-        Log.wtf(TAG, "  isNexusLauncherUnderneath: ${isNexusLauncherUnderneath()}")
+        Log.wtf(TAG, "  isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
         Log.wtf(TAG, "  !notificationShadeWindowController.isLaunchingActivity: " +
                 "${!notificationShadeWindowController.isLaunchingActivity}")
         Log.wtf(TAG, "  launcherUnlockController != null: ${launcherUnlockController != null}")
@@ -1050,7 +1050,7 @@
 
         // If our launcher isn't underneath, then we're unlocking to an app or custom launcher,
         // neither of which have a smartspace.
-        if (!isNexusLauncherUnderneath()) {
+        if (!isSupportedLauncherUnderneath()) {
             return false
         }
 
@@ -1120,11 +1120,11 @@
     }
 
     /**
-     * Return whether the Google Nexus launcher is underneath the keyguard, vs. some other
-     * launcher or an app. If so, we can communicate with it to perform in-window/shared element
-     * transitions!
+     * Return whether a launcher which supports coordinated transition is underneath the keyguard,
+     * vs. some other launcher or an app. If so, we can communicate with it to perform
+     * in-window/shared element transitions!
      */
-    fun isNexusLauncherUnderneath(): Boolean {
+    fun isSupportedLauncherUnderneath(): Boolean {
         return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
                 .runningTask?.topActivity?.className?.equals(it) }
                 ?: false
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 1761ca8..f500017 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -137,7 +137,8 @@
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 keyguardStateController,
-                shadeInteractor
+                shadeInteractor,
+                { keyguardStatusViewController!!.getClockController() },
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7e826b8..7678f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2832,7 +2832,8 @@
             // playing in-window animations for this particular unlock since a previous unlock might
             // have changed the Launcher state.
             if (mWakeAndUnlocking
-                    && mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
+                    && mKeyguardUnlockAnimationControllerLazy.get()
+                            .isSupportedLauncherUnderneath()) {
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
@@ -2946,9 +2947,12 @@
             } else {
                 Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
 
-                mKeyguardViewControllerLazy.get().hide(
-                        mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
-                        mHideAnimation.getDuration());
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    mKeyguardViewControllerLazy.get().hide(
+                            mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
+                            mHideAnimation.getDuration());
+                }
+
                 onKeyguardExitFinished();
             }
 
@@ -3286,7 +3290,7 @@
             // of the in-window animations are reflected. This is needed even if we're not actually
             // playing in-window animations for this particular unlock since a previous unlock might
             // have changed the Launcher state.
-            if (mKeyguardUnlockAnimationControllerLazy.get().isNexusLauncherUnderneath()) {
+            if (mKeyguardUnlockAnimationControllerLazy.get().isSupportedLauncherUnderneath()) {
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
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 b2e436c..8bf2bc3 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
@@ -19,17 +19,22 @@
 
 import android.content.Context
 import androidx.annotation.DimenRes
-import com.android.systemui.res.R
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
@@ -54,6 +59,18 @@
             .mapLatest { burnInHelperWrapper.burnInProgressOffset() }
             .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset())
 
+    val keyguardBurnIn: Flow<BurnInModel> =
+        combine(
+                burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
+                burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
+                    it * 2 -
+                        context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
+                }
+            ) { translationX, translationY ->
+                BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
+            }
+            .distinctUntilChanged()
+
     /**
      * 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
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
new file mode 100644
index 0000000..02d1471
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BurnInModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Clock burn-in translation/scaling data */
+data class BurnInModel(
+    val translationX: Int = 0,
+    val translationY: Int = 0,
+    val scale: Float = 0f,
+    val scaleClockOnly: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 053727a..ac4ad39 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -23,7 +23,6 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
-import com.android.systemui.res.R
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
@@ -32,12 +31,15 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import javax.inject.Provider
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
@@ -57,6 +59,7 @@
         chipbarCoordinator: ChipbarCoordinator,
         keyguardStateController: KeyguardStateController,
         shadeInteractor: ShadeInteractor,
+        clockControllerProvider: Provider<ClockController>?,
     ): DisposableHandle {
         val disposableHandle =
             view.repeatWhenAttached {
@@ -83,10 +86,37 @@
 
                     if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
                         launch {
-                            viewModel.translationY.collect {
-                                val statusView =
-                                    view.requireViewById<View>(R.id.keyguard_status_view)
-                                statusView.translationY = it
+                            viewModel.translationY.collect { y ->
+                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
+                                burnInLayer.translationY = y
+                            }
+                        }
+                    }
+
+                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        launch {
+                            viewModel.translationX.collect { x ->
+                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
+                                burnInLayer.translationX = x
+                            }
+                        }
+                    }
+
+                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                        launch {
+                            viewModel.scale.collect { (scale, scaleClockOnly) ->
+                                if (scaleClockOnly) {
+                                    val largeClock =
+                                        view.findViewById<View?>(R.id.lockscreen_clock_view_large)
+                                    largeClock?.let {
+                                        it.scaleX = scale
+                                        it.scaleY = scale
+                                    }
+                                } else {
+                                    val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer)
+                                    burnInLayer.scaleX = scale
+                                    burnInLayer.scaleY = scale
+                                }
                             }
                         }
                     }
@@ -141,6 +171,7 @@
                     }
                 }
             }
+        viewModel.clockControllerProvider = clockControllerProvider
 
         onLayoutChangeListener = OnLayoutChange(viewModel)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 864e345..231eeb5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -38,7 +38,6 @@
 import androidx.core.view.isInvisible
 import com.android.keyguard.ClockEventController
 import com.android.keyguard.KeyguardClockSwitch
-import com.android.systemui.res.R
 import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -62,6 +61,7 @@
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.shared.clocks.DefaultClockController
@@ -320,6 +320,7 @@
                 chipbarCoordinator,
                 keyguardStateController,
                 shadeInteractor,
+                null, // clock provider only needed for burn in
             )
         )
         rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 1eeb017..e8df1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -19,6 +19,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
+import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -51,6 +52,7 @@
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
     splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
+    aodBurnInSection: AodBurnInSection,
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
@@ -66,6 +68,7 @@
             defaultNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
+            aodBurnInSection,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index a9885fc..ce76f56 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -46,6 +47,7 @@
     splitShadeGuidelines: SplitShadeGuidelines,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
     aodNotificationIconsSection: AodNotificationIconsSection,
+    aodBurnInSection: AodBurnInSection,
 ) : KeyguardBlueprint {
     override val id: String = SHORTCUTS_BESIDE_UDFPS
 
@@ -61,6 +63,7 @@
             defaultNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
+            aodBurnInSection,
         )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
new file mode 100644
index 0000000..09caf45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Adds a layer to group elements for translation for burn-in preventation */
+class AodBurnInSection
+@Inject
+constructor(
+    private val context: Context,
+    private val featureFlags: FeatureFlags,
+) : KeyguardSection() {
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            return
+        }
+
+        val statusView = constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
+        val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
+        val burnInLayer =
+            Layer(context).apply {
+                id = R.id.burn_in_layer
+                addView(nic)
+                addView(statusView)
+            }
+        constraintLayout.addView(burnInLayer)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            return
+        }
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            return
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(R.id.burn_in_layer)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index c49af4d..89835fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -17,22 +17,32 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
+import com.android.app.animation.Interpolators
 import com.android.systemui.common.shared.model.SharedNotificationContainerPosition
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.plugins.ClockController
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardRootViewModel
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
+    private val burnInInteractor: BurnInInteractor,
 ) {
 
     data class PreviewMode(val isInPreviewMode: Boolean = false)
@@ -44,6 +54,8 @@
      */
     private val previewMode = MutableStateFlow(PreviewMode())
 
+    public var clockControllerProvider: Provider<ClockController>? = null
+
     /** Represents the current state of the KeyguardRootView visibility */
     val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> =
         keyguardInteractor.keyguardRootViewVisibilityState
@@ -58,7 +70,34 @@
             }
         }
 
-    val translationY: Flow<Float> = keyguardInteractor.keyguardTranslationY
+    private val burnIn: Flow<BurnInModel> =
+        combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn
+            ->
+            val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
+            val useScaleOnly =
+                clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false
+            if (useScaleOnly) {
+                BurnInModel(
+                    translationX = 0,
+                    translationY = 0,
+                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
+                )
+            } else {
+                BurnInModel(
+                    translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(),
+                    translationY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt(),
+                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
+                    scaleClockOnly = true,
+                )
+            }
+        }
+
+    val translationY: Flow<Float> =
+        merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() })
+
+    val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() }
+
+    val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) }
 
     /**
      * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 1943b34..67531ad 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -30,6 +30,8 @@
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.log.table.TableLogBufferFactory;
 import com.android.systemui.qs.QSFragmentLegacy;
+import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
+import com.android.systemui.qs.pipeline.shared.TileSpec;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.wakelock.WakeLockLog;
@@ -37,6 +39,9 @@
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Dagger module for providing instances of {@link LogBuffer}.
  */
@@ -173,8 +178,35 @@
     @Provides
     @SysUISingleton
     @QSLog
-    public static LogBuffer provideQuickSettingsLogBuffer(LogBufferFactory factory) {
-        return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
+    public static LogBuffer provideQuickSettingsLogBuffer(
+            LogBufferFactory factory,
+            QSPipelineFlagsRepository flags
+    ) {
+        if (flags.getPipelineTilesEnabled()) {
+            // we use
+            return factory.create("QSLog", 450 /* maxSize */, false /* systrace */);
+        } else {
+            return factory.create("QSLog", 700 /* maxSize */, false /* systrace */);
+        }
+    }
+
+    /**
+     * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
+     * unique for each tile.
+     * go/qs-tile-refactor
+     */
+    @Provides
+    @QSTilesDefaultLog
+    public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
+        return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
+    }
+
+    @Provides
+    @QSTilesLogBuffers
+    public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
+        final Map<TileSpec, LogBuffer> buffers = new HashMap<>();
+        // Add chatty buffers here
+        return buffers;
     }
 
     /** Provides a logging buffer for logs related to Quick Settings configuration. */
@@ -420,7 +452,7 @@
 
     /**
      * Provides a {@link LogBuffer} for use by
-     *  {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
+     * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
      */
     @Provides
     @SysUISingleton
@@ -431,7 +463,7 @@
 
     /**
      * Provides a {@link LogBuffer} for use by classes in the
-     *  {@link com.android.systemui.keyguard.bouncer} package.
+     * {@link com.android.systemui.keyguard.bouncer} package.
      */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
new file mode 100644
index 0000000..6575cdd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
+ * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
+ * tile, add one to the map provided by the [QSTilesLogBuffers]
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSTilesDefaultLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt
new file mode 100644
index 0000000..62d49fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesLogBuffers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Provides a map with custom [com.android.systemui.log.LogBuffer] for QS tiles messages. Add
+ * buffers to it when the tile needs to be more verbose and the default buffer provided by
+ * [QSTilesDefaultLog] is not enough.
+ *
+ * This is not a multibinding. Add new logs directly to [LogModule]
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSTilesLogBuffers
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java
new file mode 100644
index 0000000..b0c2f8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesVerboseLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for QS tiles messages. It's used exclusively in
+ * {@link com.android.systemui.qs.tiles.base.logging.QSTileLogger}
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface QSTilesVerboseLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 19012e2..fa18b35b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -27,11 +27,11 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.res.R;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -562,6 +562,12 @@
         if (shouldNotRunAnimation(tilesToReveal)) {
             return;
         }
+        // This method has side effects (beings the fake drag, if it returns true). If we have
+        // decided that we want to do a tile reveal, we do a last check to verify that we can
+        // actually perform a fake drag.
+        if (!beginFakeDrag()) {
+            return;
+        }
 
         final int lastPageNumber = mPages.size() - 1;
         final TileLayout lastPage = mPages.get(lastPageNumber);
@@ -596,8 +602,10 @@
     }
 
     private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+        // None of these have side effects. That way, we don't need to rely on short-circuiting
+        // behavior
         boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
-        boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
+        boolean scrollingInProgress = getScrollX() != 0 || !isFakeDragging();
         // checking mRunningInTestHarness to disable animation in functional testing as it caused
         // flakiness and is not needed there. Alternative solutions were more complex and would
         // still be either potentially flaky or modify internal data.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 128c237..051eeb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -72,7 +72,8 @@
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
 
-public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
+public class CustomTile extends QSTileImpl<State> implements TileChangeListener,
+        CustomTileInterface {
     public static final String PREFIX = "custom(";
 
     private static final long CUSTOM_STALE_TIMEOUT = DateUtils.HOUR_IN_MILLIS;
@@ -181,7 +182,8 @@
     private void updateDefaultTileAndIcon() {
         try {
             PackageManager pm = mUserContext.getPackageManager();
-            int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+            int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_DIRECT_BOOT_AWARE;
             if (isSystemApp(pm)) {
                 flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
             }
@@ -213,7 +215,7 @@
      * Compare two icons, only works for resources.
      */
     private boolean iconEquals(@Nullable android.graphics.drawable.Icon icon1,
-                               @Nullable android.graphics.drawable.Icon icon2) {
+            @Nullable android.graphics.drawable.Icon icon2) {
         if (icon1 == icon2) {
             return true;
         }
@@ -252,10 +254,12 @@
         }
     }
 
+    @Override
     public int getUser() {
         return mUser;
     }
 
+    @Override
     public ComponentName getComponent() {
         return mComponent;
     }
@@ -265,6 +269,7 @@
         return super.populate(logMaker).setComponentName(mComponent);
     }
 
+    @Override
     public Tile getQsTile() {
         // TODO(b/191145007) Move to background thread safely
         updateDefaultTileAndIcon();
@@ -276,6 +281,7 @@
      *
      * @param tile tile populated with state to apply
      */
+    @Override
     public void updateTileState(Tile tile, int appUid) {
         mServiceUid = appUid;
         // This comes from a binder call IQSService.updateQsTile
@@ -310,10 +316,12 @@
         mTile.setState(tile.getState());
     }
 
+    @Override
     public void onDialogShown() {
         mIsShowingDialog = true;
     }
 
+    @Override
     public void onDialogHidden() {
         mIsShowingDialog = false;
         try {
@@ -507,6 +515,7 @@
         return mComponent.getPackageName();
     }
 
+    @Override
     public void startUnlockAndRun() {
         mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
             try {
@@ -518,8 +527,10 @@
 
     /**
      * Starts an {@link android.app.Activity}
+     *
      * @param pendingIntent A PendingIntent for an Activity to be launched immediately.
      */
+    @Override
     public void startActivityAndCollapse(PendingIntent pendingIntent) {
         if (!pendingIntent.isActivity()) {
             Log.i(TAG, "Intent not for activity.");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt
new file mode 100644
index 0000000..9e02320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTileInterface.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.service.quicksettings.Tile
+
+interface CustomTileInterface {
+
+    val user: Int
+    val qsTile: Tile
+    val component: ComponentName
+
+    fun getTileSpec(): String
+
+    fun refreshState()
+    fun updateTileState(tile: Tile, uid: Int)
+
+    fun onDialogShown()
+    fun onDialogHidden()
+
+    fun startActivityAndCollapse(pendingIntent: PendingIntent)
+
+    fun startUnlockAndRun()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index fc24022..acee8e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -67,9 +67,10 @@
     static final int REDUCED_MAX_BOUND = 1;
     private static final String TAG = "TileServices";
 
-    private final ArrayMap<CustomTile, TileServiceManager> mServices = new ArrayMap<>();
-    private final SparseArrayMap<ComponentName, CustomTile> mTiles = new SparseArrayMap<>();
-    private final ArrayMap<IBinder, CustomTile> mTokenMap = new ArrayMap<>();
+    private final ArrayMap<CustomTileInterface, TileServiceManager> mServices = new ArrayMap<>();
+    private final SparseArrayMap<ComponentName, CustomTileInterface> mTiles =
+            new SparseArrayMap<>();
+    private final ArrayMap<IBinder, CustomTileInterface> mTokenMap = new ArrayMap<>();
     private final Context mContext;
     private final Handler mMainHandler;
     private final Provider<Handler> mHandlerProvider;
@@ -120,7 +121,7 @@
         return mHost;
     }
 
-    public TileServiceManager getTileWrapper(CustomTile tile) {
+    public TileServiceManager getTileWrapper(CustomTileInterface tile) {
         ComponentName component = tile.getComponent();
         int userId = tile.getUser();
         TileServiceManager service = onCreateTileService(component, mBroadcastDispatcher);
@@ -140,7 +141,7 @@
                 broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
     }
 
-    public void freeService(CustomTile tile, TileServiceManager service) {
+    public void freeService(CustomTileInterface tile, TileServiceManager service) {
         synchronized (mServices) {
             service.setBindAllowed(false);
             service.handleDestroy();
@@ -184,7 +185,7 @@
         }
     }
 
-    private int verifyCaller(CustomTile tile) {
+    private int verifyCaller(CustomTileInterface tile) {
         try {
             String packageName = tile.getComponent().getPackageName();
             int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
@@ -201,7 +202,7 @@
     private void requestListening(ComponentName component) {
         synchronized (mServices) {
             int userId = mUserTracker.getUserId();
-            CustomTile customTile = getTileForUserAndComponent(userId, component);
+            CustomTileInterface customTile = getTileForUserAndComponent(userId, component);
             if (customTile == null) {
                 Log.d(TAG, "Couldn't find tile for " + component + "(" + userId + ")");
                 return;
@@ -227,7 +228,7 @@
 
     @Override
     public void updateQsTile(Tile tile, IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             int uid = verifyCaller(customTile);
             synchronized (mServices) {
@@ -247,7 +248,7 @@
 
     @Override
     public void onStartSuccessful(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             synchronized (mServices) {
@@ -267,7 +268,7 @@
 
     @Override
     public void onShowDialog(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             customTile.onDialogShown();
@@ -278,7 +279,7 @@
 
     @Override
     public void onDialogHidden(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false);
@@ -288,7 +289,7 @@
 
     @Override
     public void onStartActivity(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             mPanelInteractor.forceCollapsePanels();
@@ -301,7 +302,7 @@
     }
 
     @VisibleForTesting
-    protected void startActivity(CustomTile customTile, PendingIntent pendingIntent) {
+    protected void startActivity(CustomTileInterface customTile, PendingIntent pendingIntent) {
         if (customTile != null) {
             verifyCaller(customTile);
             customTile.startActivityAndCollapse(pendingIntent);
@@ -310,7 +311,7 @@
 
     @Override
     public void updateStatusIcon(IBinder token, Icon icon, String contentDescription) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             try {
@@ -340,7 +341,7 @@
     @Nullable
     @Override
     public Tile getTile(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             return customTile.getQsTile();
@@ -367,7 +368,7 @@
 
     @Override
     public void startUnlockAndRun(IBinder token) {
-        CustomTile customTile = getTileForToken(token);
+        CustomTileInterface customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
             customTile.startUnlockAndRun();
@@ -385,14 +386,14 @@
     }
 
     @Nullable
-    public CustomTile getTileForToken(IBinder token) {
+    public CustomTileInterface getTileForToken(IBinder token) {
         synchronized (mServices) {
             return mTokenMap.get(token);
         }
     }
 
     @Nullable
-    private CustomTile getTileForUserAndComponent(int userId, ComponentName component) {
+    private CustomTileInterface getTileForUserAndComponent(int userId, ComponentName component) {
         synchronized (mServices) {
             return mTiles.get(userId, component);
         }
@@ -419,11 +420,6 @@
     };
 
     private static final Comparator<TileServiceManager> SERVICE_SORT =
-            new Comparator<TileServiceManager>() {
-        @Override
-        public int compare(TileServiceManager left, TileServiceManager right) {
-            return -Integer.compare(left.getBindPriority(), right.getBindPriority());
-        }
-    };
+            (left, right) -> -Integer.compare(left.getBindPriority(), right.getBindPriority());
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
index 38fe34e..42d3f81 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/dagger/FooterActionsModule.kt
@@ -18,8 +18,6 @@
 
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import dagger.Binds
@@ -28,7 +26,6 @@
 /** Dagger module to provide/bind footer actions singletons. */
 @Module
 interface FooterActionsModule {
-    @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository
 
     @Binds
     fun foregroundServicesRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8b2c3de..c91ed13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -38,10 +38,10 @@
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
 import com.android.systemui.security.data.repository.SecurityRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
 import com.android.systemui.user.domain.interactor.UserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt
new file mode 100644
index 0000000..0d15a5b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalytics.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.QSEvent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Tracks QS tiles analytic events to [UiEventLogger]. */
+@SysUISingleton
+class QSTileAnalytics
+@Inject
+constructor(
+    private val uiEventLogger: UiEventLogger,
+) {
+
+    fun trackUserAction(config: QSTileConfig, action: QSTileUserAction) {
+        logAction(config, action)
+    }
+
+    private fun logAction(config: QSTileConfig, action: QSTileUserAction) {
+        uiEventLogger.logWithInstanceId(
+            action.getQSEvent(),
+            0,
+            config.metricsSpec,
+            config.instanceId,
+        )
+    }
+
+    private fun QSTileUserAction.getQSEvent(): QSEvent =
+        when (this) {
+            is QSTileUserAction.Click -> QSEvent.QS_ACTION_CLICK
+            is QSTileUserAction.LongClick -> QSEvent.QS_ACTION_LONG_PRESS
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
new file mode 100644
index 0000000..70a683b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.logging
+
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.QSTilesDefaultLog
+import com.android.systemui.log.dagger.QSTilesLogBuffers
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.StatusBarState
+import javax.inject.Inject
+import javax.inject.Provider
+
+@SysUISingleton
+class QSTileLogger
+@Inject
+constructor(
+    @QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
+    @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+    private val mStatusBarStateController: StatusBarStateController,
+) {
+    @GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
+
+    /**
+     * Tracks user action when it's first received by the ViewModel and before it reaches the
+     * pipeline
+     */
+    fun logUserAction(
+        userAction: QSTileUserAction,
+        tileSpec: TileSpec,
+        hasData: Boolean,
+        hasTileState: Boolean,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                {
+                    str1 = userAction.toLogString()
+                    int1 = mStatusBarStateController.state
+                    bool1 = hasTileState
+                    bool2 = hasData
+                },
+                {
+                    "tile $str1: " +
+                        "statusBarState=${StatusBarState.toString(int1)}, " +
+                        "hasState=$bool1, " +
+                        "hasData=$bool2"
+                }
+            )
+    }
+
+    /** Tracks user action when it's rejected by false gestures */
+    fun logUserActionRejectedByFalsing(
+        userAction: QSTileUserAction,
+        tileSpec: TileSpec,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                { str1 = userAction.toLogString() },
+                { "tile $str1: rejected by falsing" }
+            )
+    }
+
+    /** Tracks user action when it's rejected according to the policy */
+    fun logUserActionRejectedByPolicy(
+        userAction: QSTileUserAction,
+        tileSpec: TileSpec,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                { str1 = userAction.toLogString() },
+                { "tile $str1: rejected by policy" }
+            )
+    }
+
+    /**
+     * Tracks user actions when it reaches the pipeline and mixes with the last tile state and data
+     */
+    fun <T> logUserActionPipeline(
+        tileSpec: TileSpec,
+        userAction: QSTileUserAction,
+        tileState: QSTileState,
+        data: T,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                {
+                    str1 = userAction.toLogString()
+                    str2 = tileState.toLogString()
+                    str3 = data.toString().take(DATA_MAX_LENGTH)
+                },
+                {
+                    "tile $str1 pipeline: " +
+                        "statusBarState=${StatusBarState.toString(int1)}, " +
+                        "state=$str2, " +
+                        "data=$str3"
+                }
+            )
+    }
+
+    /** Tracks state changes based on the data and trigger event. */
+    fun <T> logStateUpdate(
+        tileSpec: TileSpec,
+        trigger: StateUpdateTrigger,
+        tileState: QSTileState,
+        data: T,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                {
+                    str1 = trigger.toLogString()
+                    str2 = tileState.toLogString()
+                    str3 = data.toString().take(DATA_MAX_LENGTH)
+                },
+                { "tile state update: trigger=$str1, state=$str2, data=$str3" }
+            )
+    }
+
+    private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}"
+
+    private fun TileSpec.getLogBuffer(): LogBuffer =
+        synchronized(logBufferCache) {
+            logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+        }
+
+    private fun StateUpdateTrigger.toLogString(): String =
+        when (this) {
+            is StateUpdateTrigger.ForceUpdate -> "force"
+            is StateUpdateTrigger.InitialRequest -> "init"
+            is StateUpdateTrigger.UserAction<*> -> action.toLogString()
+        }
+
+    private fun QSTileUserAction.toLogString(): String =
+        when (this) {
+            is QSTileUserAction.Click -> "click"
+            is QSTileUserAction.LongClick -> "long click"
+        }
+
+    /* Shortened version of a data class toString() */
+    private fun QSTileState.toLogString(): String =
+        "[label=$label, " +
+            "state=$activationState, " +
+            "s_label=$secondaryLabel, " +
+            "cd=$contentDescription, " +
+            "sd=$stateDescription, " +
+            "svi=$sideViewIcon, " +
+            "enabled=$enabledState, " +
+            "a11y=$expandedAccessibilityClassName" +
+            "]"
+
+    private companion object {
+        const val TAG_FORMAT_PREFIX = "QSLog"
+        const val DATA_MAX_LENGTH = 50
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index 58a335e..2114751 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -20,12 +20,15 @@
 import androidx.annotation.VisibleForTesting
 import com.android.internal.util.Preconditions
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
 import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
@@ -33,6 +36,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.kotlin.throttle
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -70,6 +74,9 @@
     private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
     private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
     private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+    private val falsingManager: FalsingManager,
+    private val qsTileAnalytics: QSTileAnalytics,
+    private val qsTileLogger: QSTileLogger,
     private val backgroundDispatcher: CoroutineDispatcher,
     private val tileScope: CoroutineScope,
 ) : QSTileViewModel {
@@ -81,6 +88,9 @@
         @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
         @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
         disabledByPolicyInteractor: DisabledByPolicyInteractor,
+        falsingManager: FalsingManager,
+        qsTileAnalytics: QSTileAnalytics,
+        qsTileLogger: QSTileLogger,
         @Background backgroundDispatcher: CoroutineDispatcher,
     ) : this(
         config,
@@ -88,6 +98,9 @@
         tileDataInteractor,
         mapper,
         disabledByPolicyInteractor,
+        falsingManager,
+        qsTileAnalytics,
+        qsTileLogger,
         backgroundDispatcher,
         CoroutineScope(SupervisorJob())
     )
@@ -98,8 +111,10 @@
         MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     private val forceUpdates: MutableSharedFlow<Unit> =
         MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    private val spec
+        get() = config.tileSpec
 
-    private lateinit var tileData: SharedFlow<DATA_TYPE>
+    private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>>
 
     override lateinit var state: SharedFlow<QSTileState>
     override val isAvailable: StateFlow<Boolean> =
@@ -128,8 +143,14 @@
 
     @CallSuper
     override fun onActionPerformed(userAction: QSTileUserAction) {
-        Preconditions.checkState(tileData.replayCache.isNotEmpty())
         Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+
+        qsTileLogger.logUserAction(
+            userAction,
+            spec,
+            tileData.replayCache.isNotEmpty(),
+            state.replayCache.isNotEmpty()
+        )
         userInputs.tryEmit(userAction)
     }
 
@@ -142,7 +163,16 @@
                 state =
                     tileData
                         // TODO(b/299908705): log data and corresponding tile state
-                        .map { mapper.map(config, it) }
+                        .map { dataWithTrigger ->
+                            mapper.map(config, dataWithTrigger.data).also { state ->
+                                qsTileLogger.logStateUpdate(
+                                    spec,
+                                    dataWithTrigger.trigger,
+                                    state,
+                                    dataWithTrigger.data
+                                )
+                            }
+                        }
                         .flowOn(backgroundDispatcher)
                         .shareIn(
                             tileScope,
@@ -158,7 +188,7 @@
         currentLifeState = lifecycle
     }
 
-    private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+    private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
         userIds
             .flatMapLatest { userId ->
                 merge(
@@ -180,7 +210,7 @@
                         request.trigger.tileData as DATA_TYPE,
                     )
                 }
-                dataFlow
+                dataFlow.map { DataWithTrigger(it, request.trigger) }
             }
             .flowOn(backgroundDispatcher)
             .shareIn(
@@ -193,21 +223,53 @@
         data class StateWithData<T>(val state: QSTileState, val data: T)
 
         return when (config.policy) {
-            is QSTilePolicy.NoRestrictions -> userInputs
-            is QSTilePolicy.Restricted ->
-                userInputs.filter {
-                    val result =
-                        disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
-                    !disabledByPolicyInteractor.handlePolicyResult(result)
+                is QSTilePolicy.NoRestrictions -> userInputs
+                is QSTilePolicy.Restricted ->
+                    userInputs.filter { action ->
+                        val result =
+                            disabledByPolicyInteractor.isDisabled(
+                                userId,
+                                config.policy.userRestriction
+                            )
+                        !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
+                            if (isDisabled) {
+                                qsTileLogger.logUserActionRejectedByPolicy(action, spec)
+                            }
+                        }
+                    }
+            }
+            .filter { action ->
+                val isFalseAction =
+                    when (action) {
+                        is QSTileUserAction.Click ->
+                            falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+                        is QSTileUserAction.LongClick ->
+                            falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+                    }
+                if (isFalseAction) {
+                    qsTileLogger.logUserActionRejectedByFalsing(action, spec)
                 }
-        // Skip the input until there is some data
-        }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
-            input,
-            stateWithData ->
-            StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
-        }
+                !isFalseAction
+            }
+            .throttle(500)
+            // Skip the input until there is some data
+            .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
+                input,
+                stateWithData ->
+                StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also {
+                    qsTileLogger.logUserActionPipeline(
+                        spec,
+                        it.action,
+                        stateWithData.state,
+                        stateWithData.data
+                    )
+                    qsTileAnalytics.trackUserAction(config, it.action)
+                }
+            }
     }
 
+    private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
+
     interface Factory<T> {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
index efad9ec..8957fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
@@ -40,7 +40,7 @@
     @Application private val coroutineScope: CoroutineScope,
 ) {
 
-    internal val updateBluetoothStateFlow: StateFlow<Boolean?> =
+    internal val bluetoothStateUpdate: StateFlow<Boolean?> =
         conflatedCallbackFlow {
                 val listener =
                     object : BluetoothCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 6815a73..8ae2dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -26,6 +26,8 @@
 import android.widget.ImageView
 import android.widget.Switch
 import android.widget.TextView
+import androidx.recyclerview.widget.AsyncListDiffer
+import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.logging.UiEventLogger
@@ -42,24 +44,26 @@
 internal class BluetoothTileDialog
 constructor(
     private val bluetoothToggleInitialValue: Boolean,
+    private val subtitleResIdInitialValue: Int,
     private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
     private val uiEventLogger: UiEventLogger,
     context: Context,
 ) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {
 
-    private val mutableBluetoothStateSwitchedFlow: MutableStateFlow<Boolean> =
+    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
         MutableStateFlow(bluetoothToggleInitialValue)
-    internal val bluetoothStateSwitchedFlow
-        get() = mutableBluetoothStateSwitchedFlow.asStateFlow()
+    internal val bluetoothStateToggle
+        get() = mutableBluetoothStateToggle.asStateFlow()
 
-    private val mutableClickedFlow: MutableSharedFlow<Pair<DeviceItem, Int>> =
+    private val mutableDeviceItemClick: MutableSharedFlow<DeviceItem> =
         MutableSharedFlow(extraBufferCapacity = 1)
-    internal val deviceItemClickedFlow
-        get() = mutableClickedFlow.asSharedFlow()
+    internal val deviceItemClick
+        get() = mutableDeviceItemClick.asSharedFlow()
 
     private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)
 
     private lateinit var toggleView: Switch
+    private lateinit var subtitleTextView: TextView
     private lateinit var doneButton: View
     private lateinit var seeAllViewGroup: View
     private lateinit var pairNewDeviceViewGroup: View
@@ -74,6 +78,7 @@
         setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null))
 
         toggleView = requireViewById(R.id.bluetooth_toggle)
+        subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
         doneButton = requireViewById(R.id.done_button)
         seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
         pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
@@ -84,6 +89,7 @@
         setupToggle()
         setupRecyclerView()
 
+        subtitleTextView.text = context.getString(subtitleResIdInitialValue)
         doneButton.setOnClickListener { dismiss() }
         seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
         pairNewDeviceText.setOnClickListener {
@@ -91,7 +97,6 @@
         }
     }
 
-    // TODO(b/298124674): use DiffUtil or AsyncListDiffer to avoid updating the whole list
     internal fun onDeviceItemUpdated(
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
@@ -102,18 +107,15 @@
         deviceItemAdapter.refreshDeviceItemList(deviceItem)
     }
 
-    internal fun onDeviceItemUpdatedAtPosition(deviceItem: DeviceItem, position: Int) {
-        deviceItemAdapter.refreshDeviceItem(deviceItem, position)
-    }
-
-    internal fun onBluetoothStateUpdated(isEnabled: Boolean) {
+    internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) {
         toggleView.isChecked = isEnabled
+        subtitleTextView.text = context.getString(subtitleResId)
     }
 
     private fun setupToggle() {
         toggleView.isChecked = bluetoothToggleInitialValue
         toggleView.setOnCheckedChangeListener { _, isChecked ->
-            mutableBluetoothStateSwitchedFlow.value = isChecked
+            mutableBluetoothStateToggle.value = isChecked
             uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
         }
     }
@@ -128,7 +130,32 @@
     internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
         RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
 
-        private val deviceItem: MutableList<DeviceItem> = mutableListOf()
+        private val diffUtilCallback =
+            object : DiffUtil.ItemCallback<DeviceItem>() {
+                override fun areItemsTheSame(
+                    deviceItem1: DeviceItem,
+                    deviceItem2: DeviceItem
+                ): Boolean {
+                    return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
+                }
+
+                override fun areContentsTheSame(
+                    deviceItem1: DeviceItem,
+                    deviceItem2: DeviceItem
+                ): Boolean {
+                    return deviceItem1.type == deviceItem2.type &&
+                        deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
+                        deviceItem1.deviceName == deviceItem2.deviceName &&
+                        deviceItem1.connectionSummary == deviceItem2.connectionSummary &&
+                        // Ignored the icon drawable
+                        deviceItem1.iconWithDescription?.second ==
+                            deviceItem2.iconWithDescription?.second &&
+                        deviceItem1.background == deviceItem2.background &&
+                        deviceItem1.isEnabled == deviceItem2.isEnabled
+                }
+            }
+
+        private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)
 
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
             val view =
@@ -137,29 +164,21 @@
             return DeviceItemViewHolder(view)
         }
 
-        override fun getItemCount() = deviceItem.size
+        override fun getItemCount() = asyncListDiffer.currentList.size
 
         override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
             val item = getItem(position)
-            holder.bind(item, position, onClickCallback)
+            holder.bind(item, onClickCallback)
         }
 
-        internal fun getItem(position: Int) = deviceItem[position]
+        internal fun getItem(position: Int) = asyncListDiffer.currentList[position]
 
         internal fun refreshDeviceItemList(updated: List<DeviceItem>) {
-            deviceItem.clear()
-            deviceItem.addAll(updated)
-            notifyDataSetChanged()
-        }
-
-        internal fun refreshDeviceItem(updated: DeviceItem, position: Int) {
-            deviceItem[position] = updated
-            notifyItemChanged(position)
+            asyncListDiffer.submitList(updated)
         }
 
         internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
             private val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-            private val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
             private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
             private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
             private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
@@ -167,17 +186,15 @@
 
             internal fun bind(
                 item: DeviceItem,
-                position: Int,
                 deviceItemOnClickCallback: BluetoothTileDialogCallback
             ) {
                 container.apply {
                     isEnabled = item.isEnabled
-                    alpha = item.alpha
-                    background = item.background
-                }
-                deviceView.setOnClickListener {
-                    mutableClickedFlow.tryEmit(Pair(item, position))
-                    uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
+                    background = item.background?.let { context.getDrawable(it) }
+                    setOnClickListener {
+                        mutableDeviceItemClick.tryEmit(item)
+                        uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
+                    }
                 }
                 nameView.text = item.deviceName
                 summaryView.text = item.connectionSummary
@@ -195,8 +212,6 @@
     }
 
     internal companion object {
-        const val ENABLED_ALPHA = 1.0f
-        const val DISABLED_ALPHA = 0.3f
         const val MAX_DEVICE_ITEM_ENTRY = 3
         const val ACTION_BLUETOOTH_DEVICE_DETAILS =
             "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 012484f..97e1783 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -21,7 +21,9 @@
 import android.os.Bundle
 import android.view.View
 import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -31,6 +33,7 @@
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PAIR_NEW_DEVICE
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
 import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -73,14 +76,26 @@
         job =
             coroutineScope.launch(mainDispatcher) {
                 dialog = createBluetoothTileDialog(context)
-                view?.let { dialogLaunchAnimator.showFromView(dialog!!, it) } ?: dialog!!.show()
+                view?.let {
+                    dialogLaunchAnimator.showFromView(
+                        dialog!!,
+                        it,
+                        animateBackgroundBoundsChange = true,
+                        cuj =
+                            DialogCuj(
+                                InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                INTERACTION_JANK_TAG
+                            )
+                    )
+                }
+                    ?: dialog!!.show()
                 updateDeviceItemJob?.cancel()
                 updateDeviceItemJob = launch { deviceItemInteractor.updateDeviceItems(context) }
 
-                bluetoothStateInteractor.updateBluetoothStateFlow
+                bluetoothStateInteractor.bluetoothStateUpdate
                     .filterNotNull()
                     .onEach {
-                        dialog!!.onBluetoothStateUpdated(it)
+                        dialog!!.onBluetoothStateUpdated(it, getSubtitleResId(it))
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
                             deviceItemInteractor.updateDeviceItems(context)
@@ -88,7 +103,7 @@
                     }
                     .launchIn(this)
 
-                deviceItemInteractor.updateDeviceItemsFlow
+                deviceItemInteractor.deviceItemUpdateRequest
                     .onEach {
                         updateDeviceItemJob?.cancel()
                         updateDeviceItemJob = launch {
@@ -97,7 +112,7 @@
                     }
                     .launchIn(this)
 
-                deviceItemInteractor.deviceItemFlow
+                deviceItemInteractor.deviceItemUpdate
                     .filterNotNull()
                     .onEach {
                         dialog!!.onDeviceItemUpdated(
@@ -109,17 +124,13 @@
                     .launchIn(this)
 
                 dialog!!
-                    .bluetoothStateSwitchedFlow
+                    .bluetoothStateToggle
                     .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
                     .launchIn(this)
 
                 dialog!!
-                    .deviceItemClickedFlow
-                    .onEach {
-                        if (deviceItemInteractor.updateDeviceItemOnClick(it.first)) {
-                            dialog!!.onDeviceItemUpdatedAtPosition(it.first, it.second)
-                        }
-                    }
+                    .deviceItemClick
+                    .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
                     .launchIn(this)
             }
     }
@@ -127,6 +138,7 @@
     private fun createBluetoothTileDialog(context: Context): BluetoothTileDialog {
         return BluetoothTileDialog(
                 bluetoothStateInteractor.isBluetoothEnabled,
+                getSubtitleResId(bluetoothStateInteractor.isBluetoothEnabled),
                 this@BluetoothTileDialogViewModel,
                 uiEventLogger,
                 context
@@ -175,6 +187,13 @@
             )
         }
     }
+
+    companion object {
+        private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
+        private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
+            if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
+            else R.string.bt_is_off
+    }
 }
 
 internal interface BluetoothTileDialogCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
index 03ae5e8..50eaf38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
@@ -34,7 +34,6 @@
 
 import android.graphics.drawable.Drawable
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ENABLED_ALPHA
 
 enum class DeviceItemType {
     AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
@@ -48,7 +47,6 @@
     val deviceName: String = "",
     val connectionSummary: String = "",
     val iconWithDescription: Pair<Drawable, String>? = null,
-    val background: Drawable? = null,
-    var isEnabled: Boolean = true,
-    var alpha: Float = ENABLED_ALPHA
+    val background: Int? = null,
+    var isEnabled: Boolean = true
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index a16a9f1..8c22614 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.res.R
 
 private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
+private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
+private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
 private val connected = R.string.quick_settings_bluetooth_device_connected
 private val saved = R.string.quick_settings_bluetooth_device_saved
 
@@ -57,11 +59,8 @@
                 BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
                     Pair(p.first, p.second)
                 },
-            background = context.getDrawable(backgroundOn),
+            background = backgroundOn,
             isEnabled = !cachedDevice.isBusy,
-            alpha =
-                if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
-                else BluetoothTileDialog.ENABLED_ALPHA,
         )
     }
 }
@@ -85,10 +84,8 @@
                 BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
                     Pair(p.first, p.second)
                 },
+            background = backgroundOn,
             isEnabled = !cachedDevice.isBusy,
-            alpha =
-                if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
-                else BluetoothTileDialog.ENABLED_ALPHA,
         )
     }
 }
@@ -112,10 +109,8 @@
                 BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
                     Pair(p.first, p.second)
                 },
+            background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
             isEnabled = !cachedDevice.isBusy,
-            alpha =
-                if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
-                else BluetoothTileDialog.ENABLED_ALPHA,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index fcd0ce6..14d24f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -55,11 +55,12 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
-    private val mutableDeviceItemFlow: MutableStateFlow<List<DeviceItem>?> = MutableStateFlow(null)
-    internal val deviceItemFlow
-        get() = mutableDeviceItemFlow.asStateFlow()
+    private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> =
+        MutableStateFlow(null)
+    internal val deviceItemUpdate
+        get() = mutableDeviceItemUpdate.asStateFlow()
 
-    internal val updateDeviceItemsFlow: SharedFlow<Unit> =
+    internal val deviceItemUpdateRequest: SharedFlow<Unit> =
         conflatedCallbackFlow {
                 val listener =
                     object : BluetoothCallback {
@@ -120,7 +121,7 @@
         withContext(backgroundDispatcher) {
             val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices
 
-            mutableDeviceItemFlow.value =
+            mutableDeviceItemUpdate.value =
                 bluetoothTileDialogRepository.cachedDevices
                     .mapNotNull { cachedDevice ->
                         deviceItemFactoryList
@@ -143,28 +144,20 @@
         )
     }
 
-    internal fun updateDeviceItemOnClick(deviceItem: DeviceItem): Boolean {
-        var isClicked = false
+    internal fun updateDeviceItemOnClick(deviceItem: DeviceItem) {
         when (deviceItem.type) {
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
                 if (!BluetoothUtils.isActiveMediaDevice(deviceItem.cachedBluetoothDevice)) {
                     deviceItem.cachedBluetoothDevice.setActive()
                     uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
-                    isClicked = true
                 }
             }
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {}
             DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
                 deviceItem.cachedBluetoothDevice.connect()
                 uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
-                isClicked = true
             }
         }
-        if (isClicked) {
-            deviceItem.isEnabled = false
-            deviceItem.alpha = BluetoothTileDialog.DISABLED_ALPHA
-        }
-        return isClicked
     }
 
     internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
index 1a6cf99..4a3bcae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt
@@ -26,6 +26,7 @@
     val tileIcon: Icon,
     @StringRes val tileLabelRes: Int,
     val instanceId: InstanceId,
+    val metricsSpec: String = tileSpec.spec,
     val policy: QSTilePolicy = QSTilePolicy.NoRestrictions,
 )
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 3501b6b..bd43307 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -456,6 +456,7 @@
                         currentUser);
             } catch (IOException | IllegalStateException e) {
                 Log.e(TAG, "Error saving screen recording: " + e.getMessage());
+                e.printStackTrace();
                 showErrorToast(R.string.screenrecord_save_error);
                 mNotificationManager.cancelAsUser(null, mNotificationId, currentUser);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index a821729..d3d38e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4619,7 +4619,9 @@
             setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
 
             // Update Clock Pivot (used by anti-burnin transformations)
-            mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
+            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+                mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
+            }
 
             int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange();
             positionClockAndNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 249c831..e2de37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
 import com.android.systemui.statusbar.phone.LightBarController
@@ -49,6 +51,11 @@
     abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
 
     @Binds
+    abstract fun bindKeyguardStatusBarRepository(
+        impl: KeyguardStatusBarRepositoryImpl
+    ): KeyguardStatusBarRepository
+
+    @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
     abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
new file mode 100644
index 0000000..8136de9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import android.content.Context
+import com.android.internal.R
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Repository for data that's specific to the status bar **on keyguard**. For data that applies to
+ * all status bars, use [StatusBarModeRepository].
+ */
+interface KeyguardStatusBarRepository {
+    /** True if we can show the user switcher on keyguard and false otherwise. */
+    val isKeyguardUserSwitcherEnabled: Flow<Boolean>
+}
+
+@SysUISingleton
+class KeyguardStatusBarRepositoryImpl
+@Inject
+constructor(
+    context: Context,
+    configurationController: ConfigurationController,
+    userSwitcherRepository: UserSwitcherRepository,
+) : KeyguardStatusBarRepository {
+    private val relevantConfigChanges: Flow<Unit> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+            val callback =
+                object : ConfigurationController.ConfigurationListener {
+                    override fun onSmallestScreenWidthChanged() {
+                        trySend(Unit)
+                    }
+
+                    override fun onDensityOrFontScaleChanged() {
+                        trySend(Unit)
+                    }
+                }
+            configurationController.addCallback(callback)
+            awaitClose { configurationController.removeCallback(callback) }
+        }
+
+    private val isKeyguardUserSwitcherConfigEnabled: Flow<Boolean> =
+        // The config depends on screen size and user enabled settings, so re-fetch whenever any of
+        // those change.
+        merge(userSwitcherRepository.isEnabled.map {}, relevantConfigChanges).map {
+            context.resources.getBoolean(R.bool.config_keyguardUserSwitcher)
+        }
+
+    /** True if we can show the user switcher on keyguard and false otherwise. */
+    override val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+        combine(
+            userSwitcherRepository.isEnabled,
+            isKeyguardUserSwitcherConfigEnabled,
+        ) { isEnabled, isKeyguardEnabled ->
+            isEnabled && isKeyguardEnabled
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.kt
new file mode 100644
index 0000000..e0c30e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/KeyguardStatusBarInteractor.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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class KeyguardStatusBarInteractor
+@Inject
+constructor(
+    keyguardStatusBarRepository: KeyguardStatusBarRepository,
+) {
+    /** True if we can show the user switcher on keyguard and false otherwise. */
+    val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+        keyguardStatusBarRepository.isKeyguardUserSwitcherEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index a54687c..20241c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -30,7 +30,6 @@
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.internal.util.ContrastColorUtil
 import com.android.settingslib.Utils
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
@@ -39,6 +38,7 @@
 import com.android.systemui.flags.ViewRefactorFlag
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationMediaManager
@@ -104,6 +104,7 @@
     private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
     private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
     private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
+    private val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
     private val tintAreas = ArrayList<Rect>()
 
     private var iconSize = 0
@@ -275,7 +276,9 @@
             return
         }
         if (screenOffAnimationController.shouldAnimateAodIcons()) {
-            aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+            if (!statusViewMigrated) {
+                aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
+            }
             aodIcons!!.alpha = 0f
             animateInAodIconTranslation()
             aodIcons!!
@@ -286,7 +289,9 @@
                 .start()
         } else {
             aodIcons!!.alpha = 1.0f
-            aodIcons!!.translationY = 0f
+            if (!statusViewMigrated) {
+                aodIcons!!.translationY = 0f
+            }
         }
     }
 
@@ -598,12 +603,14 @@
     }
 
     private fun animateInAodIconTranslation() {
-        aodIcons!!
-            .animate()
-            .setInterpolator(Interpolators.DECELERATE_QUINT)
-            .translationY(0f)
-            .setDuration(AOD_ICONS_APPEAR_DURATION)
-            .start()
+        if (!statusViewMigrated) {
+            aodIcons!!
+                .animate()
+                .setInterpolator(Interpolators.DECELERATE_QUINT)
+                .translationY(0f)
+                .setDuration(AOD_ICONS_APPEAR_DURATION)
+                .start()
+        }
     }
 
     private fun reloadAodColor() {
@@ -670,7 +677,9 @@
                 }
             } else {
                 aodIcons!!.alpha = 1.0f
-                aodIcons!!.translationY = 0f
+                if (!statusViewMigrated) {
+                    aodIcons!!.translationY = 0f
+                }
                 aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 4e81d0c..69453c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -127,7 +127,8 @@
             ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
             long additionalDelay) {
 
-        processAnimationEvents(mAnimationEvents);
+        // Animation events might generate custom animations, which are started async
+        boolean anyCustomAnimationCreated = processAnimationEvents(mAnimationEvents);
 
         int childCount = mHostLayout.getChildCount();
         mAnimationFilter.applyCombination(mNewEvents);
@@ -150,8 +151,8 @@
             initAnimationProperties(child, viewState, animationStaggerCount);
             viewState.animateTo(child, mAnimationProperties);
         }
-        if (!isRunning()) {
-            // no child has preformed any animation, lets finish
+        if (!isRunning() && !anyCustomAnimationCreated) {
+            // no child has performed any animation or is about to animate, lets finish
             onAnimationFinished();
         }
         mHeadsUpAppearChildren.clear();
@@ -335,12 +336,15 @@
     }
 
     /**
-     * Process the animationEvents for a new animation
+     * Process the animationEvents for a new animation. Here is the place to do something custom,
+     * like to modify the ViewState or to create a custom animation for an event.
      *
      *  @param animationEvents the animation events for the animation to perform
+     * @return true if any custom animation was created
      */
-    private void processAnimationEvents(
+    private boolean processAnimationEvents(
             ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
+        boolean needsCustomAnimation = false;
         for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
             final ExpandableView changingView = (ExpandableView) event.mChangingView;
             boolean loggable = false;
@@ -425,7 +429,8 @@
                 }
                 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
                         0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
-                        postAnimation, null);
+                        postAnimation, getGlobalAnimationFinishedListener());
+                needsCustomAnimation = true;
             } else if (event.animationType ==
                 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
                 if (mHostLayout.isFullySwipedOut(changingView)) {
@@ -479,7 +484,6 @@
                         needsAnimation = false;
                     }
                 }
-
                 if (needsAnimation) {
                     // We need to add the global animation listener, since once no animations are
                     // running anymore, the panel will instantly hide itself. We need to wait until
@@ -503,9 +507,11 @@
                 } else if (endRunnable != null) {
                     endRunnable.run();
                 }
+                needsCustomAnimation |= needsAnimation;
             }
             mNewEvents.add(event);
         }
+        return needsCustomAnimation;
     }
 
     public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 430b0e9..63591d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -21,12 +21,14 @@
 import android.content.pm.PackageManager
 import android.hardware.biometrics.BiometricSourceType
 import android.provider.Settings
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm
@@ -35,6 +37,10 @@
 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.tuner.TunerService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 import java.io.PrintWriter
 import javax.inject.Inject
 
@@ -43,6 +49,7 @@
 
     private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
+    private val shadeRepository: ShadeRepository
     private val devicePostureController: DevicePostureController
     @BypassOverride private val bypassOverride: Int
     private var hasFaceFeature: Boolean
@@ -107,16 +114,18 @@
     @Inject
     constructor(
         context: Context,
+        @Application applicationScope: CoroutineScope,
         tunerService: TunerService,
         statusBarStateController: StatusBarStateController,
         lockscreenUserManager: NotificationLockscreenUserManager,
         keyguardStateController: KeyguardStateController,
-        shadeExpansionStateManager: ShadeExpansionStateManager,
+        shadeRepository: ShadeRepository,
         devicePostureController: DevicePostureController,
         dumpManager: DumpManager
     ) {
         this.mKeyguardStateController = keyguardStateController
         this.statusBarStateController = statusBarStateController
+        this.shadeRepository = shadeRepository
         this.devicePostureController = devicePostureController
 
         bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
@@ -128,6 +137,7 @@
             return
         }
 
+
         if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
             devicePostureController.addCallback { posture ->
                 if (postureState != posture) {
@@ -137,7 +147,7 @@
             }
         }
 
-        dumpManager.registerDumpable("KeyguardBypassController", this)
+        dumpManager.registerNormalDumpable("KeyguardBypassController", this)
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onStateChanged(newState: Int) {
                 if (newState != StatusBarState.KEYGUARD) {
@@ -146,27 +156,36 @@
             }
         })
 
-        shadeExpansionStateManager.addQsExpansionListener { isQsExpanded ->
-            val changed = qsExpanded != isQsExpanded
-            qsExpanded = isQsExpanded
-            if (changed && !isQsExpanded) {
-                maybePerformPendingUnlock()
-            }
-        }
+        listenForQsExpandedChange(applicationScope)
 
         val dismissByDefault = if (context.resources.getBoolean(
-                        com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+                com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+
         tunerService.addTunable({ key, _ ->
             bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
+
         lockscreenUserManager.addUserChangedListener(
-                object : NotificationLockscreenUserManager.UserChangedListener {
-                    override fun onUserChanged(userId: Int) {
-                        pendingUnlock = null
-                    }
-                })
+            object : NotificationLockscreenUserManager.UserChangedListener {
+                override fun onUserChanged(userId: Int) {
+                    pendingUnlock = null
+                }
+            })
     }
 
+    @VisibleForTesting
+    fun listenForQsExpandedChange(scope: CoroutineScope) =
+        scope.launch {
+            shadeRepository.qsExpansion.map { it > 0f }.distinctUntilChanged()
+                .collect { isQsExpanded ->
+                    val changed = qsExpanded != isQsExpanded
+                    qsExpanded = isQsExpanded
+                    if (changed && !isQsExpanded) {
+                        maybePerformPendingUnlock()
+                    }
+                }
+        }
+
     private fun notifyListeners() = listeners.forEach { it.onBypassStateChanged(bypassEnabled) }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 7efa705..58126ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -43,9 +43,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -367,15 +367,22 @@
         mMultiUserAvatar.setImageDrawable(picture);
     }
 
-    /** Should only be called from {@link KeyguardStatusBarViewController}. */
-    void onBatteryLevelChanged(boolean charging) {
+    /**
+     * Should only be called from {@link KeyguardStatusBarViewController} or
+     * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}.
+     */
+    public void onBatteryChargingChanged(boolean charging) {
         if (mBatteryCharging != charging) {
             mBatteryCharging = charging;
             updateVisibilities();
         }
     }
 
-    void setKeyguardUserSwitcherEnabled(boolean enabled) {
+    /**
+     * Should only be called from {@link KeyguardStatusBarViewController} or
+     * {@link com.android.systemui.statusbar.ui.binder.KeyguardStatusBarViewBinder}.
+     */
+    public void setKeyguardUserSwitcherEnabled(boolean enabled) {
         mKeyguardUserSwitcherEnabled = enabled;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 9cf9714..2960520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -172,7 +172,7 @@
             new BatteryController.BatteryStateChangeCallback() {
                 @Override
                 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
-                    mView.onBatteryLevelChanged(charging);
+                    mView.onBatteryChargingChanged(charging);
                 }
             };
 
@@ -430,11 +430,18 @@
 
     /** Sets whether user switcher is enabled. */
     public void setKeyguardUserSwitcherEnabled(boolean enabled) {
+        if (isMigrationEnabled()) {
+            return;
+        }
         mView.setKeyguardUserSwitcherEnabled(enabled);
     }
 
     /** Sets whether this controller should listen to battery updates. */
     public void setBatteryListening(boolean listening) {
+        if (isMigrationEnabled()) {
+            return;
+        }
+
         if (listening == mBatteryListening) {
             return;
         }
@@ -472,6 +479,10 @@
 
     /** Animate the keyguard status bar in. */
     public void animateKeyguardStatusBarIn() {
+        if (isMigrationEnabled()) {
+            return;
+        }
+
         mLogger.log(TAG, LogLevel.DEBUG, "animating status bar in");
         if (mDisableStateTracker.isDisabled()) {
             // If our view is disabled, don't allow us to animate in.
@@ -488,6 +499,10 @@
 
     /** Animate the keyguard status bar out. */
     public void animateKeyguardStatusBarOut(long startDelay, long duration) {
+        if (isMigrationEnabled()) {
+            return;
+        }
+
         mLogger.log(TAG, LogLevel.DEBUG, "animating status bar out");
         ValueAnimator anim = ValueAnimator.ofFloat(mView.getAlpha(), 0f);
         anim.addUpdateListener(mAnimatorUpdateListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index 4bfce4c..660aa3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -115,6 +115,7 @@
     private int mAodIconTint;
     private boolean mAodIconsVisible;
     private boolean mShowLowPriority = true;
+    private boolean mIsStatusViewMigrated = false;
 
     @VisibleForTesting
     final NotificationListener.NotificationSettingsListener mSettingsListener =
@@ -158,7 +159,7 @@
         mStatusBarWindowController = statusBarWindowController;
         mScreenOffAnimationController = screenOffAnimationController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
-
+        mIsStatusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW);
         initializeNotificationAreaViews(context);
         reloadAodColor();
         darkIconDispatcher.addDarkReceiver(this);
@@ -557,7 +558,9 @@
             return;
         }
         if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
-            mAodIcons.setTranslationY(-mAodIconAppearTranslation);
+            if (!mIsStatusViewMigrated) {
+                mAodIcons.setTranslationY(-mAodIconAppearTranslation);
+            }
             mAodIcons.setAlpha(0);
             animateInAodIconTranslation();
             mAodIcons.animate()
@@ -567,16 +570,20 @@
                     .start();
         } else {
             mAodIcons.setAlpha(1.0f);
-            mAodIcons.setTranslationY(0);
+            if (!mIsStatusViewMigrated) {
+                mAodIcons.setTranslationY(0);
+            }
         }
     }
 
     private void animateInAodIconTranslation() {
-        mAodIcons.animate()
-                .setInterpolator(Interpolators.DECELERATE_QUINT)
-                .translationY(0)
-                .setDuration(AOD_ICONS_APPEAR_DURATION)
-                .start();
+        if (!mIsStatusViewMigrated) {
+            mAodIcons.animate()
+                    .setInterpolator(Interpolators.DECELERATE_QUINT)
+                    .translationY(0)
+                    .setDuration(AOD_ICONS_APPEAR_DURATION)
+                    .start();
+        }
     }
 
     private void reloadAodColor() {
@@ -660,7 +667,9 @@
                 }
             } else {
                 mAodIcons.setAlpha(1.0f);
-                mAodIcons.setTranslationY(0);
+                if (!mIsStatusViewMigrated) {
+                    mAodIcons.setTranslationY(0);
+                }
                 mAodIcons.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
index c63ef9e..6988e21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/binder/KeyguardStatusBarViewBinder.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView
 import com.android.systemui.statusbar.ui.viewmodel.KeyguardStatusBarViewModel
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
 
 /** Binds [KeyguardStatusBarViewModel] to [KeyguardStatusBarView]. */
 object KeyguardStatusBarViewBinder {
@@ -32,8 +34,18 @@
     ) {
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                viewModel.isVisible.collect { isVisible ->
-                    view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                launch {
+                    viewModel.isVisible.collect { isVisible ->
+                        view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+                    }
+                }
+
+                launch { viewModel.isBatteryCharging.collect { view.onBatteryChargingChanged(it) } }
+
+                launch {
+                    viewModel.isKeyguardUserSwitcherEnabled.distinctUntilChanged().collect {
+                        view.setKeyguardUserSwitcherEnabled(it)
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index ddfed87..5da01e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -16,12 +16,18 @@
 
 package com.android.systemui.statusbar.ui.viewmodel
 
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -41,6 +47,8 @@
 constructor(
     @Application scope: CoroutineScope,
     keyguardInteractor: KeyguardInteractor,
+    keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
+    batteryController: BatteryController,
 ) {
     /** True if this view should be visible and false otherwise. */
     val isVisible: StateFlow<Boolean> =
@@ -51,4 +59,26 @@
                 !isDozing && statusBarState == StatusBarState.KEYGUARD
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    /** True if the device's battery is currently charging and false otherwise. */
+    // Note: Never make this an eagerly-started state flow so that the callback is removed when the
+    // keyguard status bar view isn't attached.
+    val isBatteryCharging: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : BatteryStateChangeCallback {
+                override fun onBatteryLevelChanged(
+                    level: Int,
+                    pluggedIn: Boolean,
+                    charging: Boolean,
+                ) {
+                    trySend(charging)
+                }
+            }
+        batteryController.addCallback(callback)
+        awaitClose { batteryController.removeCallback(callback) }
+    }
+
+    /** True if we can show the user switcher on keyguard and false otherwise. */
+    val isKeyguardUserSwitcherEnabled: Flow<Boolean> =
+        keyguardStatusBarInteractor.isKeyguardUserSwitcherEnabled
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
index 9c38dc0f..3b300249 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/data/repository/TelephonyRepository.kt
@@ -17,10 +17,13 @@
 
 package com.android.systemui.telephony.data.repository
 
+import android.content.Context
+import android.content.pm.PackageManager
 import android.telephony.Annotation
 import android.telephony.TelephonyCallback
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.telephony.TelephonyListenerManager
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -30,6 +33,9 @@
 interface TelephonyRepository {
     /** The state of the current call. */
     @Annotation.CallState val callState: Flow<Int>
+
+    /** Whether the device has a radio that can be used for telephony. */
+    val hasTelephonyRadio: Boolean
 }
 
 /**
@@ -43,6 +49,7 @@
 class TelephonyRepositoryImpl
 @Inject
 constructor(
+    @Application private val applicationContext: Context,
     private val manager: TelephonyListenerManager,
 ) : TelephonyRepository {
     @Annotation.CallState
@@ -53,4 +60,7 @@
 
         awaitClose { manager.removeCallStateListener(listener) }
     }
+
+    override val hasTelephonyRadio: Boolean
+        get() = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
index 86ca33d..4642f55 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt
@@ -28,7 +28,11 @@
 class TelephonyInteractor
 @Inject
 constructor(
-    repository: TelephonyRepository,
+    private val repository: TelephonyRepository,
 ) {
     @Annotation.CallState val callState: Flow<Int> = repository.callState
+
+    /** Whether the device has a radio that can be used for telephony. */
+    val hasTelephonyRadio: Boolean
+        get() = repository.hasTelephonyRadio
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
index 18ae107..71352ef 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
@@ -23,4 +23,6 @@
 @Module
 interface UserRepositoryModule {
     @Binds fun bindRepository(impl: UserRepositoryImpl): UserRepository
+
+    @Binds fun userSwitcherRepository(impl: UserSwitcherRepositoryImpl): UserSwitcherRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
rename to packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index 5fa75ad..dc7fadd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.footer.data.repository
+package com.android.systemui.user.data.repository
 
 import android.content.Context
 import android.graphics.drawable.Drawable
@@ -22,7 +22,6 @@
 import android.os.UserManager
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -30,6 +29,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.SettingObserver
 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -48,6 +48,9 @@
 interface UserSwitcherRepository {
     /** The current [UserSwitcherStatusModel]. */
     val userSwitcherStatus: Flow<UserSwitcherStatusModel>
+
+    /** Whether the user switcher is currently enabled. */
+    val isEnabled: Flow<Boolean>
 }
 
 @SysUISingleton
@@ -66,8 +69,7 @@
     private val showUserSwitcherForSingleUser =
         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
 
-    /** Whether the user switcher is currently enabled. */
-    private val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
+    override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
         suspend fun updateState() {
             trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 61952ba..d3f83b1 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -17,10 +17,10 @@
 
 package com.android.systemui.user.ui.viewmodel
 
-import com.android.systemui.res.R
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.drawable.CircularDrawable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.GuestUserInteractor
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -42,6 +42,10 @@
     private val guestUserInteractor: GuestUserInteractor,
 ) {
 
+    /** The currently selected user. */
+    val selectedUser: Flow<UserViewModel> =
+        userInteractor.selectedUser.map { user -> toViewModel(user) }
+
     /** On-device users. */
     val users: Flow<List<UserViewModel>> =
         userInteractor.users.map { models -> models.map { user -> toViewModel(user) } }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 6b5679a..8e54eb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -301,7 +301,10 @@
     }
 
     @Test
-    fun testUdfps_onFingerDown_showDwellRipple() {
+    fun testUdfps_onFingerDown_runningForDeviceEntry_showDwellRipple() {
+        // GIVEN fingerprint detection is running on keyguard
+        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
+
         // GIVEN view is already attached
         controller.onViewAttached()
         val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
@@ -318,4 +321,21 @@
         verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f)
         verify(rippleView).startDwellRipple(false)
     }
+
+    @Test
+    fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() {
+        // GIVEN fingerprint detection is NOT running on keyguard
+        `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false)
+
+        // GIVEN view is already attached
+        controller.onViewAttached()
+        val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java)
+        verify(udfpsController).addCallback(captor.capture())
+
+        // WHEN finger is down
+        captor.value.onFingerDown()
+
+        // THEN doesn't show dwell ripple
+        verify(rippleView, never()).startDwellRipple(false)
+    }
 }
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 6308269..5eab2fc 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
@@ -24,6 +24,8 @@
 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.keyguard.shared.model.BurnInModel
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -56,6 +58,11 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         configurationRepository = FakeConfigurationRepository()
+
+        context
+            .getOrCreateTestableResources()
+            .addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset)
+
         KeyguardInteractorFactory.create().let {
             keyguardInteractor = it.keyguardInteractor
             fakeKeyguardRepository = it.repository
@@ -110,6 +117,25 @@
             assertThat(udfpsBurnInProgress).isEqualTo(burnInProgress)
         }
 
+    @Test
+    fun keyguardBurnIn() =
+        testScope.runTest {
+            whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
+
+            val burnInModel by collectLastValue(underTest.keyguardBurnIn)
+
+            // After time tick, returns the configured values
+            fakeKeyguardRepository.dozeTimeTick(10)
+            assertThat(burnInModel)
+                .isEqualTo(
+                    BurnInModel(
+                        translationX = burnInOffset.toInt(),
+                        translationY = burnInOffset.toInt(),
+                        scale = 0.5f,
+                    )
+                )
+        }
+
     private fun setBurnInProgress(progress: Float) {
         burnInProgress = progress
         whenever(burnInHelperWrapper.burnInProgressOffset()).thenReturn(burnInProgress)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 7f4755d..7940b45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
+import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
 import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -63,6 +64,7 @@
     @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection
     @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
     @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
+    @Mock private lateinit var aodBurnInSection: AodBurnInSection
 
     @Before
     fun setup() {
@@ -80,6 +82,7 @@
                 defaultNSSLSection,
                 splitShadeGuidelines,
                 aodNotificationIconsSection,
+                aodBurnInSection,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index a14a1c5..71688db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -23,17 +23,23 @@
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.google.common.truth.Truth
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.plugins.ClockController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.*
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Answers
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
@@ -45,12 +51,16 @@
     private lateinit var testScope: TestScope
     private lateinit var repository: FakeKeyguardRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
+    @Mock private lateinit var burnInInteractor: BurnInInteractor
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
+
+    private val burnInFlow = MutableStateFlow(BurnInModel())
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         val testDispatcher = StandardTestDispatcher()
         testScope = TestScope(testDispatcher)
+        MockitoAnnotations.initMocks(this)
 
         val featureFlags =
             FakeFeatureFlags().apply {
@@ -62,7 +72,9 @@
         keyguardInteractor = withDeps.keyguardInteractor
         repository = withDeps.repository
 
-        underTest = KeyguardRootViewModel(keyguardInteractor)
+        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+        underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor)
+        underTest.clockControllerProvider = Provider { clockController }
     }
 
     @Test
@@ -70,15 +82,15 @@
         testScope.runTest {
             val value = collectLastValue(underTest.alpha)
 
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
             repository.setKeyguardAlpha(0.1f)
-            Truth.assertThat(value()).isEqualTo(0.1f)
+            assertThat(value()).isEqualTo(0.1f)
             repository.setKeyguardAlpha(0.5f)
-            Truth.assertThat(value()).isEqualTo(0.5f)
+            assertThat(value()).isEqualTo(0.5f)
             repository.setKeyguardAlpha(0.2f)
-            Truth.assertThat(value()).isEqualTo(0.2f)
+            assertThat(value()).isEqualTo(0.2f)
             repository.setKeyguardAlpha(0f)
-            Truth.assertThat(value()).isEqualTo(0f)
+            assertThat(value()).isEqualTo(0f)
         }
 
     @Test
@@ -87,14 +99,85 @@
             val value = collectLastValue(underTest.alpha)
             underTest.enablePreviewMode()
 
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
             repository.setKeyguardAlpha(0.1f)
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
             repository.setKeyguardAlpha(0.5f)
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
             repository.setKeyguardAlpha(0.2f)
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
             repository.setKeyguardAlpha(0f)
-            Truth.assertThat(value()).isEqualTo(1f)
+            assertThat(value()).isEqualTo(1f)
+        }
+
+    @Test
+    fun translationAndScaleFromBurnInNotDozing() =
+        testScope.runTest {
+            val translationX by collectLastValue(underTest.translationX)
+            val translationY by collectLastValue(underTest.translationY)
+            val scale by collectLastValue(underTest.scale)
+
+            // Set to not dozing (on lockscreen)
+            repository.setDozeAmount(0f)
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
+        }
+
+    @Test
+    fun translationAndScaleFromBurnFullyDozing() =
+        testScope.runTest {
+            val translationX by collectLastValue(underTest.translationX)
+            val translationY by collectLastValue(underTest.translationY)
+            val scale by collectLastValue(underTest.scale)
+
+            // Set to dozing (on AOD)
+            repository.setDozeAmount(1f)
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(20)
+            assertThat(translationY).isEqualTo(30)
+            assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
+        }
+
+    @Test
+    fun translationAndScaleFromBurnInUseScaleOnly() =
+        testScope.runTest {
+            whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
+
+            val translationX by collectLastValue(underTest.translationX)
+            val translationY by collectLastValue(underTest.translationY)
+            val scale by collectLastValue(underTest.scale)
+
+            // Set to dozing (on AOD)
+            repository.setDozeAmount(1f)
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
new file mode 100644
index 0000000..2c4e10e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.analytics
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSEvent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileAnalyticsTest : SysuiTestCase() {
+
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+
+    private lateinit var underTest: QSTileAnalytics
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = QSTileAnalytics(uiEventLogger)
+    }
+
+    @Test
+    fun testClickIsLogged() {
+        underTest.trackUserAction(config, QSTileUserAction.Click(null))
+
+        verify(uiEventLogger)
+            .logWithInstanceId(
+                eq(QSEvent.QS_ACTION_CLICK),
+                eq(0),
+                eq("test_spec"),
+                eq(InstanceId.fakeInstanceId(0))
+            )
+    }
+
+    @Test
+    fun testLongClickIsLogged() {
+        underTest.trackUserAction(config, QSTileUserAction.LongClick(null))
+
+        verify(uiEventLogger)
+            .logWithInstanceId(
+                eq(QSEvent.QS_ACTION_LONG_PRESS),
+                eq(0),
+                eq("test_spec"),
+                eq(InstanceId.fakeInstanceId(0))
+            )
+    }
+
+    private companion object {
+
+        val config = QSTileConfigTestBuilder.build()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
new file mode 100644
index 0000000..4401e0d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.logging
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dump.LogcatEchoTrackerAlways
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileLoggerTest : SysuiTestCase() {
+
+    @Mock private lateinit var statusBarController: StatusBarStateController
+
+    private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
+    private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
+
+    private lateinit var underTest: QSTileLogger
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            QSTileLogger(
+                mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
+                { logBuffer },
+                statusBarController
+            )
+    }
+
+    @Test
+    fun testChattyLog() {
+        underTest.logUserActionRejectedByFalsing(
+            QSTileUserAction.Click(null),
+            TileSpec.create("chatty_tile"),
+        )
+        underTest.logUserActionRejectedByFalsing(
+            QSTileUserAction.Click(null),
+            TileSpec.create("chatty_tile"),
+        )
+
+        val logs = chattyLogBuffer.getStringBuffer().lines().filter { it.isNotBlank() }
+        assertThat(logs).hasSize(2)
+        logs.forEach { assertThat(it).contains("tile click: rejected by falsing") }
+    }
+
+    @Test
+    fun testLogUserAction() {
+        underTest.logUserAction(
+            QSTileUserAction.Click(null),
+            TileSpec.create("test_spec"),
+            hasData = false,
+            hasTileState = false,
+        )
+
+        assertThat(logBuffer.getStringBuffer())
+            .contains("tile click: statusBarState=SHADE, hasState=false, hasData=false")
+    }
+
+    @Test
+    fun testLogUserActionRejectedByFalsing() {
+        underTest.logUserActionRejectedByFalsing(
+            QSTileUserAction.Click(null),
+            TileSpec.create("test_spec"),
+        )
+
+        assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by falsing")
+    }
+
+    @Test
+    fun testLogUserActionRejectedByPolicy() {
+        underTest.logUserActionRejectedByPolicy(
+            QSTileUserAction.Click(null),
+            TileSpec.create("test_spec"),
+        )
+
+        assertThat(logBuffer.getStringBuffer()).contains("tile click: rejected by policy")
+    }
+
+    @Test
+    fun testLogUserActionPipeline() {
+        underTest.logUserActionPipeline(
+            TileSpec.create("test_spec"),
+            QSTileUserAction.Click(null),
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+            "test_data",
+        )
+
+        assertThat(logBuffer.getStringBuffer())
+            .contains(
+                "tile click pipeline: " +
+                    "statusBarState=SHADE, " +
+                    "state=[" +
+                    "label=, " +
+                    "state=INACTIVE, " +
+                    "s_label=null, " +
+                    "cd=null, " +
+                    "sd=null, " +
+                    "svi=None, " +
+                    "enabled=ENABLED, " +
+                    "a11y=null" +
+                    "], " +
+                    "data=test_data"
+            )
+    }
+
+    @Test
+    fun testLogStateUpdate() {
+        underTest.logStateUpdate(
+            TileSpec.create("test_spec"),
+            StateUpdateTrigger.ForceUpdate,
+            QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
+            "test_data",
+        )
+
+        assertThat(logBuffer.getStringBuffer())
+            .contains(
+                "tile state update: " +
+                    "trigger=force, " +
+                    "state=[" +
+                    "label=, " +
+                    "state=INACTIVE, " +
+                    "s_label=null, " +
+                    "cd=null, " +
+                    "sd=null, " +
+                    "svi=None, " +
+                    "enabled=ENABLED, " +
+                    "a11y=null" +
+                    "], " +
+                    "data=test_data"
+            )
+    }
+
+    private fun LogBuffer.getStringBuffer(): String {
+        val stringWriter = StringWriter()
+        dump(PrintWriter(stringWriter), 0)
+        return stringWriter.buffer.toString()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index 89fa55b3..8b66040 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -29,8 +29,6 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.DISABLED_ALPHA
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ENABLED_ALPHA
 import com.android.systemui.res.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -62,6 +60,8 @@
 
     @Mock private lateinit var uiEventLogger: UiEventLogger
 
+    private val subtitleResId = R.string.quick_settings_bluetooth_tile_subtitle
+
     private lateinit var icon: Pair<Drawable, String>
     private lateinit var bluetoothTileDialog: BluetoothTileDialog
     private lateinit var deviceItem: DeviceItem
@@ -69,7 +69,13 @@
     @Before
     fun setUp() {
         bluetoothTileDialog =
-            BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+            BluetoothTileDialog(
+                ENABLED,
+                subtitleResId,
+                bluetoothTileDialogCallback,
+                uiEventLogger,
+                mContext
+            )
         icon = Pair(drawable, DEVICE_NAME)
         deviceItem =
             DeviceItem(
@@ -99,7 +105,13 @@
     @Test
     fun testShowDialog_displayBluetoothDevice() {
         bluetoothTileDialog =
-            BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+            BluetoothTileDialog(
+                ENABLED,
+                subtitleResId,
+                bluetoothTileDialogCallback,
+                uiEventLogger,
+                mContext
+            )
         bluetoothTileDialog.show()
         bluetoothTileDialog.onDeviceItemUpdated(
             listOf(deviceItem),
@@ -118,49 +130,61 @@
     @Test
     fun testDeviceItemViewHolder_cachedDeviceNotBusy() {
         deviceItem.isEnabled = true
-        deviceItem.alpha = ENABLED_ALPHA
 
         val view =
             LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
         val viewHolder =
-            BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+            BluetoothTileDialog(
+                    ENABLED,
+                    subtitleResId,
+                    bluetoothTileDialogCallback,
+                    uiEventLogger,
+                    mContext
+                )
                 .Adapter(bluetoothTileDialogCallback)
                 .DeviceItemViewHolder(view)
-        viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback)
-        val container = view.requireViewById<View>(R.id.bluetooth_device)
-        val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
+        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
+        val container = view.requireViewById<View>(R.id.bluetooth_device_row)
 
         assertThat(container).isNotNull()
         assertThat(container.isEnabled).isTrue()
-        assertThat(container.alpha).isEqualTo(ENABLED_ALPHA)
-        assertThat(deviceView.hasOnClickListeners()).isTrue()
+        assertThat(container.hasOnClickListeners()).isTrue()
     }
 
     @Test
     fun testDeviceItemViewHolder_cachedDeviceBusy() {
         deviceItem.isEnabled = false
-        deviceItem.alpha = DISABLED_ALPHA
 
         val view =
             LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
         val viewHolder =
-            BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+            BluetoothTileDialog(
+                    ENABLED,
+                    subtitleResId,
+                    bluetoothTileDialogCallback,
+                    uiEventLogger,
+                    mContext
+                )
                 .Adapter(bluetoothTileDialogCallback)
                 .DeviceItemViewHolder(view)
-        viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback)
+        viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
         val container = view.requireViewById<View>(R.id.bluetooth_device_row)
-        val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
 
         assertThat(container).isNotNull()
         assertThat(container.isEnabled).isFalse()
-        assertThat(container.alpha).isEqualTo(DISABLED_ALPHA)
-        assertThat(deviceView.hasOnClickListeners()).isTrue()
+        assertThat(container.hasOnClickListeners()).isTrue()
     }
 
     @Test
     fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
         bluetoothTileDialog =
-            BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+            BluetoothTileDialog(
+                ENABLED,
+                subtitleResId,
+                bluetoothTileDialogCallback,
+                uiEventLogger,
+                mContext
+            )
         bluetoothTileDialog.show()
         bluetoothTileDialog.onDeviceItemUpdated(
             listOf(deviceItem),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index 7157cce..a0ff2ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -95,10 +95,11 @@
                 testScope.backgroundScope,
                 dispatcher,
             )
-        `when`(deviceItemInteractor.deviceItemFlow).thenReturn(MutableStateFlow(null).asStateFlow())
-        `when`(bluetoothStateInteractor.updateBluetoothStateFlow)
+        `when`(deviceItemInteractor.deviceItemUpdate)
             .thenReturn(MutableStateFlow(null).asStateFlow())
-        `when`(deviceItemInteractor.updateDeviceItemsFlow)
+        `when`(bluetoothStateInteractor.bluetoothStateUpdate)
+            .thenReturn(MutableStateFlow(null).asStateFlow())
+        `when`(deviceItemInteractor.deviceItemUpdateRequest)
             .thenReturn(MutableStateFlow(Unit).asStateFlow())
         `when`(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
     }
@@ -143,7 +144,7 @@
             bluetoothTileDialogViewModel.showDialog(context, null)
 
             assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
-            verify(deviceItemInteractor).deviceItemFlow
+            verify(deviceItemInteractor).deviceItemUpdate
         }
     }
 
@@ -153,7 +154,7 @@
             bluetoothTileDialogViewModel.showDialog(context, null)
 
             assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
-            verify(bluetoothStateInteractor).updateBluetoothStateFlow
+            verify(bluetoothStateInteractor).bluetoothStateUpdate
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
index 3451902..92c7326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
@@ -69,7 +69,7 @@
         val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
-        assertThat(deviceItem.background).isNull()
+        assertThat(deviceItem.background).isNotNull()
     }
 
     private fun assertDeviceItem(deviceItem: DeviceItem?, deviceItemType: DeviceItemType) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index 07a95ae..3593075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -109,7 +109,7 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).isEmpty()
+            assertThat(interactor.deviceItemUpdate.value).isEmpty()
         }
     }
 
@@ -123,7 +123,7 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).isEmpty()
+            assertThat(interactor.deviceItemUpdate.value).isEmpty()
         }
     }
 
@@ -137,8 +137,8 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).hasSize(1)
-            assertThat(interactor.deviceItemFlow.value!![0]).isEqualTo(deviceItem1)
+            assertThat(interactor.deviceItemUpdate.value).hasSize(1)
+            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1)
         }
     }
 
@@ -152,9 +152,9 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).hasSize(2)
-            assertThat(interactor.deviceItemFlow.value!![0]).isEqualTo(deviceItem2)
-            assertThat(interactor.deviceItemFlow.value!![1]).isEqualTo(deviceItem2)
+            assertThat(interactor.deviceItemUpdate.value).hasSize(2)
+            assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2)
+            assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2)
         }
     }
 
@@ -179,7 +179,8 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(interactor.deviceItemUpdate.value)
+                .isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
@@ -201,7 +202,8 @@
 
             interactor.updateDeviceItems(mContext)
 
-            assertThat(interactor.deviceItemFlow.value).isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(interactor.deviceItemUpdate.value)
+                .isEqualTo(listOf(deviceItem2, deviceItem1))
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 9024c6c..4760dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -1,21 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.systemui.qs.tiles.viewmodel
 
-import android.graphics.drawable.ShapeDrawable
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.android.internal.logging.InstanceId
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
 import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
 import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.launchIn
@@ -26,6 +44,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 // TODO(b/299909368): Add more tests
 @MediumTest
@@ -34,9 +54,13 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
 
+    @Mock private lateinit var qsTileLogger: QSTileLogger
+    @Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+
     private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
     private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
     private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
+    private val fakeFalsingManager = FalsingManagerFake()
 
     private val testCoroutineDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testCoroutineDispatcher)
@@ -45,6 +69,7 @@
 
     @Before
     fun setup() {
+        MockitoAnnotations.initMocks(this)
         underTest = createViewModel(testScope)
     }
 
@@ -79,6 +104,9 @@
                     QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
             },
             fakeDisabledByPolicyInteractor,
+            fakeFalsingManager,
+            qsTileAnalytics,
+            qsTileLogger,
             testCoroutineDispatcher,
             scope.backgroundScope,
         )
@@ -88,7 +116,7 @@
         val TEST_QS_TILE_CONFIG =
             QSTileConfig(
                 TileSpec.create("default"),
-                Icon.Loaded(ShapeDrawable(), null),
+                Icon.Resource(0, null),
                 0,
                 InstanceId.fakeInstanceId(0),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
new file mode 100644
index 0000000..f1e6a05
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeKeyguardStatusBarRepository.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeKeyguardStatusBarRepository : KeyguardStatusBarRepository {
+    override val isKeyguardUserSwitcherEnabled = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
new file mode 100644
index 0000000..b1c994c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepositoryImplTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.user.data.repository.FakeUserSwitcherRepository
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.Mockito.verify
+
+@SmallTest
+class KeyguardStatusBarRepositoryImplTest : SysuiTestCase() {
+    private val testScope = TestScope()
+    private val configurationController = mock<ConfigurationController>()
+    private val userSwitcherRepository = FakeUserSwitcherRepository()
+
+    val underTest =
+        KeyguardStatusBarRepositoryImpl(
+            context,
+            configurationController,
+            userSwitcherRepository,
+        )
+
+    private val configurationListener: ConfigurationController.ConfigurationListener
+        get() {
+            val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+            verify(configurationController).addCallback(capture(captor))
+            return captor.value
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_switcherNotEnabled_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+            userSwitcherRepository.isEnabled.value = false
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_keyguardConfigNotEnabled_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+            userSwitcherRepository.isEnabled.value = true
+
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_switchEnabledAndKeyguardConfigEnabled_true() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+            userSwitcherRepository.isEnabled.value = true
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_refetchedOnSmallestWidthChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+            userSwitcherRepository.isEnabled.value = true
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+            assertThat(latest).isTrue()
+
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+            configurationListener.onSmallestScreenWidthChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_refetchedOnDensityChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+            userSwitcherRepository.isEnabled.value = true
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+            assertThat(latest).isTrue()
+
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+            configurationListener.onDensityOrFontScaleChanged()
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isKeyguardUserSwitcherEnabled_refetchedOnEnabledChanged() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isKeyguardUserSwitcherEnabled)
+
+            userSwitcherRepository.isEnabled.value = false
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, true)
+            assertThat(latest).isFalse()
+
+            // WHEN the switcher becomes enabled but the keyguard switcher becomes disabled
+            context.orCreateTestableResources.addOverride(R.bool.config_keyguardUserSwitcher, false)
+            userSwitcherRepository.isEnabled.value = true
+
+            // THEN the value is still false because the keyguard config is refetched
+            assertThat(latest).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
index 126e0e8..7caa5ccc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
@@ -64,6 +64,7 @@
                     featureFlags =
                         FakeFeatureFlagsClassicModule {
                             set(Flags.FACE_AUTH_REFACTOR, value = false)
+                            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false)
                         },
                     mocks =
                         TestMocksModule(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index ab441e3..6209f73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -20,11 +20,14 @@
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.res.R
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
@@ -34,6 +37,9 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.tuner.TunerService
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
@@ -54,6 +60,10 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardBypassControllerTest : SysuiTestCase() {
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val featureFlags = FakeFeatureFlags()
+    private val shadeRepository = FakeShadeRepository()
 
     private lateinit var keyguardBypassController: KeyguardBypassController
     private lateinit var postureControllerCallback: DevicePostureController.Callback
@@ -61,10 +71,10 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
     @Mock private lateinit var keyguardStateController: KeyguardStateController
-    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock private lateinit var devicePostureController: DevicePostureController
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var packageManager: PackageManager
+
     @Captor
     private val postureCallbackCaptor =
         ArgumentCaptor.forClass(DevicePostureController.Callback::class.java)
@@ -73,6 +83,8 @@
     @Before
     fun setUp() {
         context.setMockPackageManager(packageManager)
+        featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
         whenever(packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true)
         whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
     }
@@ -126,11 +138,12 @@
         keyguardBypassController =
             KeyguardBypassController(
                 context,
+                testScope.backgroundScope,
                 tunerService,
                 statusBarStateController,
                 lockscreenUserManager,
                 keyguardStateController,
-                shadeExpansionStateManager,
+                shadeRepository,
                 devicePostureController,
                 dumpManager
             )
@@ -267,4 +280,25 @@
 
         assertThat(keyguardBypassController.bypassEnabled).isFalse()
     }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun qsExpansion_updates() {
+        testScope.runTest {
+            initKeyguardBypassController()
+            assertThat(keyguardBypassController.qsExpanded).isFalse()
+            val job = keyguardBypassController.listenForQsExpandedChange(this)
+            shadeRepository.setQsExpansion(0.5f)
+            runCurrent()
+
+            assertThat(keyguardBypassController.qsExpanded).isTrue()
+
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            assertThat(keyguardBypassController.qsExpanded).isFalse()
+
+            job.cancel()
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index c0d248e..6484389 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -64,12 +64,13 @@
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.SceneTestUtils;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository;
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -156,7 +157,6 @@
     public void setup() throws Exception {
         mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
         mShadeViewStateProvider = new TestShadeViewStateProvider();
-        mShadeViewStateProvider = new TestShadeViewStateProvider();
 
         MockitoAnnotations.initMocks(this);
 
@@ -176,7 +176,9 @@
         mViewModel =
                 new KeyguardStatusBarViewModel(
                         mTestScope.getBackgroundScope(),
-                        mKeyguardInteractor);
+                        mKeyguardInteractor,
+                        new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
+                        mBatteryController);
 
         allowTestableLooperAsMainThread();
         TestableLooper.get(this).runWithLooper(() -> {
@@ -320,6 +322,15 @@
     }
 
     @Test
+    public void setBatteryListening_true_flagOn_callbackNotAdded() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+
+        mController.setBatteryListening(true);
+
+        verify(mBatteryController, never()).addCallback(any());
+    }
+
+    @Test
     public void updateTopClipping_viewClippingUpdated() {
         int viewTop = 20;
         mKeyguardStatusBarView.setTop(viewTop);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index f4078d5..1bc346d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -29,13 +29,23 @@
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
+import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
+import org.mockito.Mockito.verify
 
 @SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
 class KeyguardStatusBarViewModelTest : SysuiTestCase() {
     private val testScope = TestScope()
     private val sceneTestUtils = SceneTestUtils(this)
@@ -54,11 +64,18 @@
         ) {
             sceneTestUtils.sceneInteractor()
         }
+    private val keyguardStatusBarInteractor =
+        KeyguardStatusBarInteractor(
+            FakeKeyguardStatusBarRepository(),
+        )
+    private val batteryController = mock<BatteryController>()
 
     private val underTest =
         KeyguardStatusBarViewModel(
             testScope.backgroundScope,
             keyguardInteractor,
+            keyguardStatusBarInteractor,
+            batteryController,
         )
 
     @Test
@@ -102,4 +119,46 @@
 
             assertThat(latest).isTrue()
         }
+
+    @Test
+    fun isBatteryCharging_matchesCallback() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isBatteryCharging)
+            runCurrent()
+
+            val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+            verify(batteryController).addCallback(capture(captor))
+            val callback = captor.value
+
+            callback.onBatteryLevelChanged(
+                /* level= */ 2,
+                /* pluggedIn= */ false,
+                /* charging= */ true,
+            )
+
+            assertThat(latest).isTrue()
+
+            callback.onBatteryLevelChanged(
+                /* level= */ 2,
+                /* pluggedIn= */ true,
+                /* charging= */ false,
+            )
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isBatteryCharging_unregistersWhenNotListening() =
+        testScope.runTest {
+            val job = underTest.isBatteryCharging.launchIn(this)
+            runCurrent()
+
+            val captor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+            verify(batteryController).addCallback(capture(captor))
+
+            job.cancel()
+            runCurrent()
+
+            verify(batteryController).removeCallback(captor.value)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 773a0d8..0209030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -49,6 +49,7 @@
 
         underTest =
             TelephonyRepositoryImpl(
+                applicationContext = context,
                 manager = manager,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
new file mode 100644
index 0000000..758fe93a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserSwitcherRepository.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.data.repository
+
+import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeUserSwitcherRepository : UserSwitcherRepository {
+    override val isEnabled = MutableStateFlow(false)
+    override val userSwitcherStatus =
+        MutableStateFlow<UserSwitcherStatusModel>(UserSwitcherStatusModel.Disabled)
+}
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 424218c..409ba48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2262,5 +2262,8 @@
         public boolean isBubbleBarEnabled() {
             return mIsBubbleBarEnabled;
         }
+
+        @Override
+        public void refresh() {}
     }
 }
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 cd009df..d6632a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -38,16 +38,12 @@
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.FakeBroadcastDispatcher;
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
-import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
@@ -128,20 +124,8 @@
         // reference and are never sent to the Context. This will also prevent a real
         // BroadcastDispatcher from actually registering receivers.
         mDependency.injectTestDependency(BroadcastDispatcher.class, mFakeBroadcastDispatcher);
-        // A lot of tests get the FalsingManager, often via several layers of indirection.
-        // None of them actually need it.
-        mDependency.injectTestDependency(FalsingManager.class, new FalsingManagerFake());
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
 
-        // A lot of tests get the LocalBluetoothManager, often via several layers of indirection.
-        // None of them actually need it.
-        mDependency.injectMockDependency(LocalBluetoothManager.class);
-
-        // Notifications tests are injecting one of these, causing many classes (including
-        // KeyguardUpdateMonitor to be created (injected).
-        // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
-        mDependency.injectMockDependency(SmartReplyController.class);
-
         // Make sure that all tests on any SystemUIDialog does not crash because this dependency
         // is missing (constructing the actual one would throw).
         // TODO(b/219008720): Remove this.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 1a893f8..bf77b1a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -36,8 +36,6 @@
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
 import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
-import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
 import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -51,6 +49,8 @@
 import com.android.systemui.statusbar.policy.SecurityController
 import com.android.systemui.statusbar.policy.UserInfoController
 import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.data.repository.UserSwitcherRepository
+import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.settings.FakeSettings
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
new file mode 100644
index 0000000..201926d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import androidx.annotation.StringRes
+import com.android.internal.logging.InstanceId
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+object QSTileConfigTestBuilder {
+
+    fun build(configure: BuildingScope.() -> Unit = {}): QSTileConfig =
+        BuildingScope().apply(configure).build()
+
+    class BuildingScope {
+        var tileSpec: TileSpec = TileSpec.create("test_spec")
+        var tileIcon: Icon = Icon.Resource(0, ContentDescription.Resource(0))
+        @StringRes var tileLabel: Int = 0
+        var instanceId: InstanceId = InstanceId.fakeInstanceId(0)
+        var metricsSpec: String = tileSpec.spec
+        var policy: QSTilePolicy = QSTilePolicy.NoRestrictions
+
+        fun build() =
+            QSTileConfig(
+                tileSpec,
+                tileIcon,
+                tileLabel,
+                instanceId,
+                metricsSpec,
+                policy,
+            )
+    }
+}
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 6777734..766f748 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
@@ -17,6 +17,8 @@
 package com.android.systemui.scene
 
 import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
@@ -30,6 +32,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -51,12 +54,17 @@
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.ui.viewmodel.UserActionViewModel
+import com.android.systemui.user.ui.viewmodel.UserViewModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -209,6 +217,7 @@
     fun bouncerViewModel(
         bouncerInteractor: BouncerInteractor,
         authenticationInteractor: AuthenticationInteractor,
+        users: List<UserViewModel> = createUsers(),
     ): BouncerViewModel {
         return BouncerViewModel(
             applicationContext = context,
@@ -217,6 +226,13 @@
             bouncerInteractor = bouncerInteractor,
             authenticationInteractor = authenticationInteractor,
             flags = sceneContainerFlags,
+            selectedUser = flowOf(users.first { it.isSelectionMarkerVisible }),
+            users = flowOf(users),
+            userSwitcherMenu = flowOf(createMenuActions()),
+            telephonyInteractor =
+                TelephonyInteractor(
+                    repository = FakeTelephonyRepository(),
+                ),
         )
     }
 
@@ -232,6 +248,43 @@
         return testScope.backgroundScope
     }
 
+    private fun createUsers(
+        count: Int = 3,
+        selectedIndex: Int = 0,
+    ): List<UserViewModel> {
+        check(selectedIndex in 0 until count)
+
+        return buildList {
+            repeat(count) { index ->
+                add(
+                    UserViewModel(
+                        viewKey = index,
+                        name = Text.Loaded("name_$index"),
+                        image = BitmapDrawable(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)),
+                        isSelectionMarkerVisible = index == selectedIndex,
+                        alpha = 1f,
+                        onClicked = {},
+                    )
+                )
+            }
+        }
+    }
+
+    private fun createMenuActions(): List<UserActionViewModel> {
+        return buildList {
+            repeat(3) { index ->
+                add(
+                    UserActionViewModel(
+                        viewKey = index.toLong(),
+                        iconResourceId = 0,
+                        textResourceId = 0,
+                        onClicked = {},
+                    )
+                )
+            }
+        }
+    }
+
     companion object {
         fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
             return when (this) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
index 7c70846..992ac62 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/telephony/data/repository/FakeTelephonyRepository.kt
@@ -31,9 +31,16 @@
     private val _callState = MutableStateFlow(0)
     override val callState: Flow<Int> = _callState.asStateFlow()
 
+    override var hasTelephonyRadio: Boolean = true
+        private set
+
     fun setCallState(value: Int) {
         _callState.value = value
     }
+
+    fun setHasRadio(hasRadio: Boolean) {
+        this.hasTelephonyRadio = hasRadio
+    }
 }
 
 @Module
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index cad8fcf..c7b53c5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -223,6 +223,9 @@
     @GuardedBy("mFlagLock")
     private String mPccProviderHints;
 
+    @GuardedBy("mFlagLock")
+    private int mMaxInputLengthForAutofill;
+
     // Default flag values for Autofill PCC
 
     private static final String DEFAULT_PCC_FEATURE_PROVIDER_HINTS = "";
@@ -694,6 +697,10 @@
                     DeviceConfig.NAMESPACE_AUTOFILL,
                     AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS,
                     DEFAULT_PCC_FEATURE_PROVIDER_HINTS);
+            mMaxInputLengthForAutofill = DeviceConfig.getInt(
+                    DeviceConfig.NAMESPACE_AUTOFILL,
+                    AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL,
+                    AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL);
             if (verbose) {
                 Slog.v(mTag, "setDeviceConfigProperties() for PCC: "
                         + "mPccClassificationEnabled=" + mPccClassificationEnabled
@@ -988,6 +995,15 @@
         }
     }
 
+    /**
+     * Return the max suggestion length
+     */
+    public int getMaxInputLengthForAutofill() {
+        synchronized (mFlagLock) {
+            return mMaxInputLengthForAutofill;
+        }
+    }
+
     @Nullable
     @VisibleForTesting
     static Map<String, String[]> getAllowedCompatModePackages(String setting) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4e5b058..0220dec 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4596,7 +4596,8 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
-                targetLabel, targetIcon, this, mContext, id, mCompatMode);
+                targetLabel, targetIcon, this, mContext, id, mCompatMode,
+                mService.getMaster().getMaxInputLengthForAutofill());
 
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetCountShown(
@@ -4856,7 +4857,7 @@
                     public void onInflate() {
                         Session.this.onShown(UI_TYPE_INLINE);
                     }
-                });
+                }, mService.getMaster().getMaxInputLengthForAutofill());
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index d479dfb..602855d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -204,12 +204,14 @@
      * @param context context with the proper state (like display id) to show the UI
      * @param sessionId id of the autofill session
      * @param compatMode whether the app is being autofilled in compatibility mode.
+     * @param maxInputLengthForAutofill max user input to provide suggestion
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
             @Nullable String filterText, @Nullable String servicePackageName,
             @NonNull ComponentName componentName, @NonNull CharSequence serviceLabel,
             @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback,
-            @NonNull Context context, int sessionId, boolean compatMode) {
+            @NonNull Context context, int sessionId, boolean compatMode,
+            int maxInputLengthForAutofill) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
             Slogf.d(TAG, "showFillUi(): id=%s, filter=%d chars, displayId=%d", focusedId, size,
@@ -229,7 +231,8 @@
             }
             hideAllUiThread(callback);
             mFillUi = new FillUi(context, response, focusedId, filterText, mOverlayControl,
-                    serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), new FillUi.Callback() {
+                    serviceLabel, serviceIcon, mUiModeMgr.isNightMode(), maxInputLengthForAutofill,
+                    new FillUi.Callback() {
                 @Override
                 public void onResponsePicked(FillResponse response) {
                     log.setType(MetricsEvent.TYPE_DETAIL);
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 0a8fe62..b2716ec 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -127,6 +127,8 @@
 
     private final int mThemeId;
 
+    private int mMaxInputLengthForAutofill;
+
     public static boolean isFullScreen(Context context) {
         if (sFullScreenMode != null) {
             if (sVerbose) Slog.v(TAG, "forcing full-screen mode to " + sFullScreenMode);
@@ -138,7 +140,8 @@
     FillUi(@NonNull Context context, @NonNull FillResponse response,
             @NonNull AutofillId focusedViewId, @Nullable String filterText,
             @NonNull OverlayControl overlayControl, @NonNull CharSequence serviceLabel,
-            @NonNull Drawable serviceIcon, boolean nightMode, @NonNull Callback callback) {
+            @NonNull Drawable serviceIcon, boolean nightMode, int maxInputLengthForAutofill,
+            @NonNull Callback callback) {
         if (sVerbose) {
             Slogf.v(TAG, "nightMode: %b displayId: %d", nightMode, context.getDisplayId());
         }
@@ -146,6 +149,7 @@
         mCallback = callback;
         mFullScreen = isFullScreen(context);
         mContext = new ContextThemeWrapper(context, mThemeId);
+        mMaxInputLengthForAutofill = maxInputLengthForAutofill;
 
         final LayoutInflater inflater = LayoutInflater.from(mContext);
 
@@ -432,10 +436,11 @@
                     Slog.d(TAG, "No dataset matches filter with " + size + " chars");
                 }
                 mCallback.requestHideFillUi();
-            } else if (size > 3) {
-                // Do not show suggestion if user entered four or more characters
+            } else if (size > mMaxInputLengthForAutofill) {
+                // Do not show suggestion if user entered more than the maximum suggesiton length
                 if (sDebug) {
-                    Slog.d(TAG, "Not showing fill UI because user entered more than 3 characters");
+                    Slog.d(TAG, "Not showing fill UI because user entered more than "
+                            + mMaxInputLengthForAutofill + " characters");
                 }
                 mCallback.requestHideFillUi();
             } else {
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index 24eab0b..c734680 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -98,6 +98,11 @@
     }
 
     /**
+     * If user enters more characters than this length, the autofill suggestion won't be shown.
+     */
+    private int mMaxInputLengthForAutofill = Integer.MAX_VALUE;
+
+    /**
      * Encapsulates various arguments used by {@link #forAutofill} and {@link #forAugmentedAutofill}
      */
     public static class InlineFillUiInfo {
@@ -128,20 +133,22 @@
     @NonNull
     public static InlineFillUi forAutofill(@NonNull InlineFillUiInfo inlineFillUiInfo,
             @NonNull FillResponse response,
-            @NonNull InlineSuggestionUiCallback uiCallback) {
+            @NonNull InlineSuggestionUiCallback uiCallback, int maxInputLengthForAutofill) {
         if (response.getAuthentication() != null && response.getInlinePresentation() != null) {
             InlineSuggestion inlineAuthentication =
                     InlineSuggestionFactory.createInlineAuthentication(inlineFillUiInfo, response,
                             uiCallback);
-            return new InlineFillUi(inlineFillUiInfo, inlineAuthentication);
+            return new InlineFillUi(inlineFillUiInfo, inlineAuthentication,
+                    maxInputLengthForAutofill);
         } else if (response.getDatasets() != null) {
             SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions =
                     InlineSuggestionFactory.createInlineSuggestions(inlineFillUiInfo,
                             InlineSuggestionInfo.SOURCE_AUTOFILL, response.getDatasets(),
                             uiCallback);
-            return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
+            return new InlineFillUi(inlineFillUiInfo, inlineSuggestions,
+                    maxInputLengthForAutofill);
         }
-        return new InlineFillUi(inlineFillUiInfo, new SparseArray<>());
+        return new InlineFillUi(inlineFillUiInfo, new SparseArray<>(), maxInputLengthForAutofill);
     }
 
     /**
@@ -157,6 +164,9 @@
         return new InlineFillUi(inlineFillUiInfo, inlineSuggestions);
     }
 
+    /**
+     * Used by augmented autofill
+     */
     private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo,
             @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions) {
         mAutofillId = inlineFillUiInfo.mFocusedId;
@@ -171,13 +181,36 @@
         mFilterText = inlineFillUiInfo.mFilterText;
     }
 
+    /**
+     * Used by normal autofill
+     */
+    private InlineFillUi(@Nullable InlineFillUiInfo inlineFillUiInfo,
+            @NonNull SparseArray<Pair<Dataset, InlineSuggestion>> inlineSuggestions,
+            int maxInputLengthForAutofill) {
+        mAutofillId = inlineFillUiInfo.mFocusedId;
+        int size = inlineSuggestions.size();
+        mDatasets = new ArrayList<>(size);
+        mInlineSuggestions = new ArrayList<>(size);
+        for (int i = 0; i < size; i++) {
+            Pair<Dataset, InlineSuggestion> value = inlineSuggestions.valueAt(i);
+            mDatasets.add(value.first);
+            mInlineSuggestions.add(value.second);
+        }
+        mFilterText = inlineFillUiInfo.mFilterText;
+        mMaxInputLengthForAutofill = maxInputLengthForAutofill;
+    }
+
+    /**
+     * Used by normal autofill
+     */
     private InlineFillUi(@NonNull InlineFillUiInfo inlineFillUiInfo,
-            @NonNull InlineSuggestion inlineSuggestion) {
+            @NonNull InlineSuggestion inlineSuggestion, int maxInputLengthForAutofill) {
         mAutofillId = inlineFillUiInfo.mFocusedId;
         mDatasets = null;
         mInlineSuggestions = new ArrayList<>();
         mInlineSuggestions.add(inlineSuggestion);
         mFilterText = inlineFillUiInfo.mFilterText;
+        mMaxInputLengthForAutofill = maxInputLengthForAutofill;
     }
 
     /**
@@ -217,11 +250,11 @@
             return new InlineSuggestionsResponse(inlineSuggestions);
         }
 
-        // Do not show suggestion if user entered four or more characters
-        if (!TextUtils.isEmpty(mFilterText) && mFilterText.length() > 3) {
+        // Do not show inline suggestion if user entered more than a certain number of characters.
+        if (!TextUtils.isEmpty(mFilterText) && mFilterText.length() > mMaxInputLengthForAutofill) {
             if (sVerbose) {
-                Slog.v(TAG, "Not showing inline suggestion because user entered more than 3 "
-                        + "characters");
+                Slog.v(TAG, "Not showing inline suggestion when user entered more than "
+                         + mMaxInputLengthForAutofill + " characters");
             }
             return new InlineSuggestionsResponse(inlineSuggestions);
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 9dd0dca..852e36d 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -218,6 +218,9 @@
 
     void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) {
         synchronized (mGenericWindowPolicyControllerLock) {
+            if (mActivityLaunchAllowedByDefault != activityLaunchDefaultAllowed) {
+                mActivityPolicyExemptions.clear();
+            }
             mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed;
         }
     }
diff --git a/services/core/java/com/android/server/ExplicitHealthCheckController.java b/services/core/java/com/android/server/ExplicitHealthCheckController.java
index 20de40e..3d610d3 100644
--- a/services/core/java/com/android/server/ExplicitHealthCheckController.java
+++ b/services/core/java/com/android/server/ExplicitHealthCheckController.java
@@ -343,7 +343,7 @@
             };
 
             mContext.bindServiceAsUser(intent, mConnection,
-                    Context.BIND_AUTO_CREATE, UserHandle.of(UserHandle.USER_SYSTEM));
+                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
             Slog.i(TAG, "Explicit health check service is bound");
         }
     }
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c094c12..dd54334 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -522,7 +522,8 @@
         Exception res = null;
         final ContentResolver resolver = context.getContentResolver();
         try {
-            Settings.Global.resetToDefaultsAsUser(resolver, null, mode, UserHandle.USER_SYSTEM);
+            Settings.Global.resetToDefaultsAsUser(resolver, null, mode,
+                UserHandle.SYSTEM.getIdentifier());
         } catch (Exception e) {
             res = new RuntimeException("Failed to reset global settings", e);
         }
@@ -779,12 +780,13 @@
     }
 
     private static int[] getAllUserIds() {
-        int[] userIds = { UserHandle.USER_SYSTEM };
+        int systemUserId = UserHandle.SYSTEM.getIdentifier();
+        int[] userIds = { systemUserId };
         try {
             for (File file : FileUtils.listFilesOrEmpty(Environment.getDataSystemDeDirectory())) {
                 try {
                     final int userId = Integer.parseInt(file.getName());
-                    if (userId != UserHandle.USER_SYSTEM) {
+                    if (userId != systemUserId) {
                         userIds = ArrayUtils.appendInt(userIds, userId);
                     }
                 } catch (NumberFormatException ignored) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 6e984bb..b05b397 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -330,7 +330,7 @@
         String describeBlockedStateLocked() {
             final String prefix;
             if (mCurrentMonitor == null) {
-                prefix = "Blocked in handler on ";
+                prefix = "Blocked in handler";
             } else {
                 prefix =  "Blocked in monitor " + mCurrentMonitor.getClass().getName();
             }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0615ecf..a97675f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -281,8 +281,8 @@
     // don't have an oom adj assigned by the system).
     public static final int NATIVE_ADJ = -1000;
 
-    // Memory pages are 4K.
-    static final int PAGE_SIZE = 4 * 1024;
+    // Memory page size.
+    static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
 
     // Activity manager's version of an undefined schedule group
     static final int SCHED_GROUP_UNDEFINED = Integer.MIN_VALUE;
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index a680f50..cd3d2f0 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -16,6 +16,8 @@
 
 package com.android.server.graphics.fonts;
 
+import static android.graphics.fonts.FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE;
+
 import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
 
 import android.annotation.NonNull;
@@ -581,7 +583,8 @@
                     font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
         }
         FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
-                LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+                LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT,
+                VARIABLE_FONT_FAMILY_TYPE_NONE);
         return new FontConfig.NamedFamilyList(Collections.singletonList(family),
                 fontFamily.getName());
     }
diff --git a/services/core/java/com/android/server/input/InputShellCommand.java b/services/core/java/com/android/server/input/InputShellCommand.java
index 16b23ca..138186b 100644
--- a/services/core/java/com/android/server/input/InputShellCommand.java
+++ b/services/core/java/com/android/server/input/InputShellCommand.java
@@ -18,6 +18,7 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
 import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
 import static android.view.KeyEvent.KEYCODE_ALT_RIGHT;
 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
@@ -38,6 +39,11 @@
 import static android.view.KeyEvent.META_SHIFT_LEFT_ON;
 import static android.view.KeyEvent.META_SHIFT_ON;
 import static android.view.KeyEvent.META_SHIFT_RIGHT_ON;
+import static android.view.MotionEvent.AXIS_HSCROLL;
+import static android.view.MotionEvent.AXIS_SCROLL;
+import static android.view.MotionEvent.AXIS_VSCROLL;
+import static android.view.MotionEvent.AXIS_X;
+import static android.view.MotionEvent.AXIS_Y;
 
 import static java.util.Collections.unmodifiableMap;
 
@@ -47,14 +53,21 @@
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.IntArray;
+import android.util.Pair;
 import android.view.InputDevice;
+import android.view.InputEvent;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
 
 /**
  * Command that sends input events to the device.
@@ -107,15 +120,31 @@
         map.put("touchpad", InputDevice.SOURCE_TOUCHPAD);
         map.put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION);
         map.put("joystick", InputDevice.SOURCE_JOYSTICK);
+        map.put("rotaryencoder", InputDevice.SOURCE_ROTARY_ENCODER);
 
         SOURCES = unmodifiableMap(map);
     }
 
+    public InputShellCommand() {
+        this(InputShellCommand::injectInputEvent);
+    }
+
+    @VisibleForTesting
+    InputShellCommand(BiConsumer<InputEvent, Integer> inputEventInjector) {
+        mInputEventInjector = inputEventInjector;;
+    }
+
+    private static void injectInputEvent(InputEvent event, Integer injectMode) {
+        InputManagerGlobal.getInstance().injectInputEvent(event, injectMode);
+    }
+
+    private final BiConsumer<InputEvent, Integer> mInputEventInjector;
+
     private void injectKeyEvent(KeyEvent event, boolean async) {
         int injectMode = async
                 ? InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
                 : InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH;
-        InputManagerGlobal.getInstance().injectInputEvent(event, injectMode);
+        mInputEventInjector.accept(event, injectMode);
     }
 
     private int getInputDeviceId(int inputSource) {
@@ -161,19 +190,41 @@
      */
     private void injectMotionEvent(int inputSource, int action, long downTime, long when,
             float x, float y, float pressure, int displayId) {
+        final Map<Integer, Float> axisValues =
+                Map.of(
+                        MotionEvent.AXIS_X, x,
+                        MotionEvent.AXIS_Y, y,
+                        MotionEvent.AXIS_PRESSURE, pressure);
+        injectMotionEvent(inputSource, action, downTime, when, axisValues, displayId);
+    }
+
+    /**
+     * Builds a MotionEvent and injects it into the event stream.
+     *
+     * @param inputSource the InputDevice.SOURCE_* sending the input event
+     * @param action the MotionEvent.ACTION_* for the event
+     * @param downTime the value of the ACTION_DOWN event happened
+     * @param when the value of SystemClock.uptimeMillis() at which the event happened
+     * @param axisValues a map of an axis to the respective axis value
+     * @param displayId the ID of the display associated to the event
+     */
+    private void injectMotionEvent(int inputSource, int action, long downTime, long when,
+            Map<Integer, Float> axisValues, int displayId) {
         final int pointerCount = 1;
         MotionEvent.PointerProperties[] pointerProperties =
                 new MotionEvent.PointerProperties[pointerCount];
-        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
         for (int i = 0; i < pointerCount; i++) {
             pointerProperties[i] = new MotionEvent.PointerProperties();
             pointerProperties[i].id = i;
             pointerProperties[i].toolType = getToolType(inputSource);
+        }
+        MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[pointerCount];
+        for (int i = 0; i < pointerCount; i++) {
             pointerCoords[i] = new MotionEvent.PointerCoords();
-            pointerCoords[i].x = x;
-            pointerCoords[i].y = y;
-            pointerCoords[i].pressure = pressure;
             pointerCoords[i].size = DEFAULT_SIZE;
+            for (var entry : axisValues.entrySet()) {
+                pointerCoords[i].setAxisValue(entry.getKey(), entry.getValue());
+            }
         }
         if (displayId == INVALID_DISPLAY
                 && (inputSource & InputDevice.SOURCE_CLASS_POINTER) != 0) {
@@ -183,7 +234,7 @@
                 pointerProperties, pointerCoords, DEFAULT_META_STATE, DEFAULT_BUTTON_STATE,
                 DEFAULT_PRECISION_X, DEFAULT_PRECISION_Y, getInputDeviceId(inputSource),
                 DEFAULT_EDGE_FLAGS, inputSource, displayId, DEFAULT_FLAGS);
-        InputManagerGlobal.getInstance().injectInputEvent(event,
+        mInputEventInjector.accept(event,
                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
     }
 
@@ -246,7 +297,9 @@
                 runPress(inputSource, displayId);
             } else if ("roll".equals(arg)) {
                 runRoll(inputSource, displayId);
-            }  else if ("motionevent".equals(arg)) {
+            }  else if ("scroll".equals(arg)) {
+                runScroll(inputSource, displayId);
+            } else if ("motionevent".equals(arg)) {
                 runMotionEvent(inputSource, displayId);
             } else if ("keycombination".equals(arg)) {
                 runKeyCombination(inputSource, displayId);
@@ -268,13 +321,18 @@
             for (String src : SOURCES.keySet()) {
                 out.println("      " + src);
             }
+            out.println("[axis_value] represents an option specifying the value of a given axis ");
+            out.println("      The syntax is as follows: --axis <axis_name>,<axis_value>");
+            out.println("            where <axis_name> is the name of the axis as defined in ");
+            out.println("            MotionEvent without the AXIS_ prefix (e.g. SCROLL, X)");
+            out.println("      Sample [axis_values] entry: `--axis Y,3`, `--axis SCROLL,-2`");
             out.println();
             out.printf("-d: specify the display ID.\n      (Default: %d for key event, "
                     + "%d for motion event if not specified.)",
                     INVALID_DISPLAY, DEFAULT_DISPLAY);
             out.println();
             out.println("The commands and default sources are:");
-            out.println("      text <string> (Default: touchscreen)");
+            out.println("      text <string> (Default: keyboard)");
             out.println("      keyevent [--longpress|--doubletap|--async"
                     + "|--delay <duration between keycodes in ms>]"
                     + " <key code number or name> ..."
@@ -287,6 +345,13 @@
             out.println("      press (Default: trackball)");
             out.println("      roll <dx> <dy> (Default: trackball)");
             out.println("      motionevent <DOWN|UP|MOVE|CANCEL> <x> <y> (Default: touchscreen)");
+            out.println("      scroll (Default: rotaryencoder). Has the following syntax:");
+            out.println("            scroll <x> <y> [axis_value] (for pointer-based sources)");
+            out.println("            scroll [axis_value] (for non-pointer-based sources)");
+            out.println("            Axis options: SCROLL, HSCROLL, VSCROLL");
+            out.println("            None or one or multiple axis value options can be specified.");
+            out.println("            To specify multiple axes, use one axis option for per axis.");
+            out.println("            Example: `scroll --axis VSCROLL,2 --axis SCROLL,-2.4`");
             out.println("      keycombination [-t duration(ms)] <key code 1> <key code 2> ..."
                     + " (Default: keyboard, the key order is important here.)");
         }
@@ -452,6 +517,62 @@
                 Float.parseFloat(getNextArgRequired()), displayId);
     }
 
+    private void runScroll(int inputSource, int displayId) {
+        inputSource = getSource(inputSource, InputDevice.SOURCE_ROTARY_ENCODER);
+        final boolean isPointerEvent = (inputSource & SOURCE_CLASS_POINTER) == SOURCE_CLASS_POINTER;
+        final Map<Integer, Float> axisValues = new HashMap<>();
+        if (isPointerEvent) {
+            axisValues.put(AXIS_X, Float.parseFloat(getNextArgRequired()));
+            axisValues.put(AXIS_Y, Float.parseFloat(getNextArgRequired()));
+        }
+        final Set<Integer> supportedAxes = Set.of(AXIS_HSCROLL, AXIS_VSCROLL, AXIS_SCROLL);
+        String nextOption;
+        while ((nextOption = getNextOption()) != null) {
+            switch (nextOption) {
+                case "--axis":
+                    final Pair<Integer, Float> axisAndValue = readAxisOptionValues(supportedAxes);
+                    axisValues.put(axisAndValue.first, axisAndValue.second);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported option: " + nextOption);
+            }
+        }
+        final long now = SystemClock.uptimeMillis();
+        injectMotionEvent(inputSource, MotionEvent.ACTION_SCROLL, now /* downTime */,
+                now /* when */, axisValues, displayId);
+    }
+
+    /**
+     * Reads an axis value for the `--axis` command option.
+     *
+     * <p>The value for an `--axis` should be a single string containing the axis name without the
+     * `AXIS_` prefix, and comma, and a float value representing the value for the respective axis.
+     *
+     * <p>Example: `--axis SCROLL,2.4` represents "a value of 2.4 for AXIS_SCROLL"
+     *
+     * <p>This method should be called after the `--axis` option has already been read.
+     *
+     * @param supportedAxes the set of allowed axes to be read. If an axis option is read where the
+     *      axis is not present in this set, this method throws an {@link IllegalArgumentException}.
+     * @return a Pair of the axis and its respective value.
+     */
+    private Pair<Integer, Float> readAxisOptionValues(Set<Integer> supportedAxes) {
+        final String optionValue = getNextArgRequired();
+        final String[] axisAndValue = optionValue.split(",");
+        if (axisAndValue.length != 2) {
+            throw new IllegalArgumentException("Invalid --axis option value: " + optionValue);
+        }
+        final String axisName = "AXIS_" + axisAndValue[0];
+        final int axis = MotionEvent.axisFromString(axisName);
+        if (axis == -1) {
+            throw new IllegalArgumentException("Invalid axis name: " + axisName);
+        }
+        if (!supportedAxes.contains(axis)) {
+            throw new IllegalArgumentException("Unsupported axis: " + axisName);
+        }
+        return Pair.create(axis, Float.parseFloat(axisAndValue[1]));
+    }
+
     /**
      * Sends a simple zero-pressure move event.
      *
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index c83a969..431aabd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -69,27 +69,16 @@
                     mIsSystemLanguage = true;
                 } else {
                     // TODO: Use Locale#getLanguage or Locale#toLanguageTag
-                    final String systemLanguage = parseLanguageFromLocaleString(systemLocale);
-                    final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+                    final String systemLanguage = LocaleUtils.getLanguageFromLocaleString(
+                            systemLocale);
+                    final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString(
+                            subtypeLocale);
                     mIsSystemLanguage = systemLanguage.length() >= 2
                             && systemLanguage.equals(subtypeLanguage);
                 }
             }
         }
 
-        /**
-         * Returns the language component of a given locale string.
-         * TODO: Use {@link Locale#getLanguage()} instead.
-         */
-        private static String parseLanguageFromLocaleString(final String locale) {
-            final int idx = locale.indexOf('_');
-            if (idx < 0) {
-                return locale;
-            } else {
-                return locale.substring(0, idx);
-            }
-        }
-
         private static int compareNullableCharSequences(@Nullable CharSequence c1,
                 @Nullable CharSequence c2) {
             // For historical reasons, an empty text needs to put at the last.
@@ -116,7 +105,7 @@
          *
          * @param other the object to be compared.
          * @return a negative integer, zero, or positive integer as this object is less than, equal
-         *         to, or greater than the specified <code>other</code> object.
+         * to, or greater than the specified <code>other</code> object.
          */
         @Override
         public int compareTo(ImeSubtypeListItem other) {
@@ -253,9 +242,10 @@
 
         /**
          * Returns the index of the specified input method and subtype in the given list.
-         * @param imi The {@link InputMethodInfo} to be searched.
+         *
+         * @param imi     The {@link InputMethodInfo} to be searched.
          * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method
-         * does not have a subtype.
+         *                does not have a subtype.
          * @return The index in the given list. -1 if not found.
          */
         private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -327,6 +317,7 @@
          * {@link #mUsageHistoryOfSubtypeListItemIndex}.
          * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
          * so as not to be confused with the index in {@link #mImeSubtypeList}.
+         *
          * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
          */
         private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index f865e60..7d090db 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -215,12 +215,7 @@
      * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
      */
     static String getLanguageFromLocaleString(String locale) {
-        final int idx = locale.indexOf('_');
-        if (idx < 0) {
-            return locale;
-        } else {
-            return locale.substring(0, idx);
-        }
+        return Locale.forLanguageTag(locale).getLanguage();
     }
 
     static Locale getSystemLocaleFromContext(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 83a3125..c9528d8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -707,7 +707,8 @@
     }
 
     private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
-        return checkCallerHasModifyAudioRoutingPermission(pid, uid);
+        return checkCallerHasModifyAudioRoutingPermission(pid, uid)
+                || checkCallerHasBluetoothPermissions(pid, uid);
     }
 
     private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index b8c2b86..1f12c88 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -50,7 +50,7 @@
 
     @NonNull
     private final Object mLock = new Object();
-    final IActivityManager mActivityManager = ActivityManager.getService();
+    IActivityManager mActivityManager;
 
     @NonNull
     @GuardedBy("mLock")
@@ -149,6 +149,9 @@
         try {
             final int[] resolvedUserIds;
             if (userIds == null) {
+                if (mActivityManager == null) {
+                    mActivityManager = ActivityManager.getService();
+                }
                 if (mActivityManager == null) return;
                 resolvedUserIds = mActivityManager.getRunningUserIds();
             } else {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 237bc92..a450b4d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -187,7 +187,6 @@
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.hardware.power.Mode;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -711,11 +710,15 @@
     static final int POWER_MODE_REASON_CHANGE_DISPLAY = 1 << 1;
     /** @see UnknownAppVisibilityController */
     static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2;
-    /** This can only be used by {@link #endLaunchPowerMode(int)}.*/
+    /**
+     * This can only be used by {@link #endPowerMode(int)}. Excluding UNKNOWN_VISIBILITY because
+     * that is guarded by a timeout while keyguard is locked.
+     */
     static final int POWER_MODE_REASON_ALL = (1 << 2) - 1;
 
-    /** The reasons to use {@link Mode#LAUNCH} power mode. */
-    private @PowerModeReason int mLaunchPowerModeReasons;
+    /** The reasons to apply power modes. */
+    @PowerModeReason
+    private int mPowerModeReasons;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
@@ -4657,11 +4660,9 @@
         }
     }
 
-    void startLaunchPowerMode(@PowerModeReason int reason) {
-        if (mPowerManagerInternal != null) {
-            mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
-        }
-        mLaunchPowerModeReasons |= reason;
+    void startPowerMode(@PowerModeReason int reason) {
+        final int prevReasons = mPowerModeReasons;
+        mPowerModeReasons |= reason;
         if ((reason & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) {
             if (mRetainPowerModeAndTopProcessState) {
                 mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG);
@@ -4671,27 +4672,56 @@
                     POWER_MODE_UNKNOWN_VISIBILITY_TIMEOUT_MS);
             Slog.d(TAG, "Temporarily retain top process state for launching app");
         }
+        if (mPowerManagerInternal == null) {
+            return;
+        }
+
+        // START_ACTIVITY can be used with UNKNOWN_VISIBILITY. CHANGE_DISPLAY should be used alone.
+        if ((reason & POWER_MODE_REASON_START_ACTIVITY) != 0
+                && (prevReasons & POWER_MODE_REASON_START_ACTIVITY) == 0) {
+            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "StartModeLaunch");
+            mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_LAUNCH, true);
+        } else if (reason == POWER_MODE_REASON_CHANGE_DISPLAY
+                && (prevReasons & POWER_MODE_REASON_CHANGE_DISPLAY) == 0) {
+            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "StartModeDisplayChange");
+            mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_DISPLAY_CHANGE, true);
+        }
     }
 
-    void endLaunchPowerMode(@PowerModeReason int reason) {
-        if (mLaunchPowerModeReasons == 0) return;
-        mLaunchPowerModeReasons &= ~reason;
+    void endPowerMode(@PowerModeReason int reason) {
+        if (mPowerModeReasons == 0) return;
+        final int prevReasons = mPowerModeReasons;
+        mPowerModeReasons &= ~reason;
 
-        if ((mLaunchPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) {
+        if ((mPowerModeReasons & POWER_MODE_REASON_UNKNOWN_VISIBILITY) != 0) {
             boolean allResolved = true;
             for (int i = mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
                 allResolved &= mRootWindowContainer.getChildAt(i).mUnknownAppVisibilityController
                         .allResolved();
             }
             if (allResolved) {
-                mLaunchPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY;
+                mPowerModeReasons &= ~POWER_MODE_REASON_UNKNOWN_VISIBILITY;
                 mRetainPowerModeAndTopProcessState = false;
                 mH.removeMessages(H.END_POWER_MODE_UNKNOWN_VISIBILITY_MSG);
             }
         }
+        if (mPowerManagerInternal == null) {
+            return;
+        }
 
-        if (mLaunchPowerModeReasons == 0 && mPowerManagerInternal != null) {
-            mPowerManagerInternal.setPowerMode(Mode.LAUNCH, false);
+        // If the launching apps have unknown visibility, only end launch power mode until the
+        // states are resolved.
+        final int endLaunchModeReasons = POWER_MODE_REASON_START_ACTIVITY
+                | POWER_MODE_REASON_UNKNOWN_VISIBILITY;
+        if ((prevReasons & endLaunchModeReasons) != 0
+                && (mPowerModeReasons & endLaunchModeReasons) == 0) {
+            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "EndModeLaunch");
+            mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_LAUNCH, false);
+        }
+        if ((prevReasons & POWER_MODE_REASON_CHANGE_DISPLAY) != 0
+                && (mPowerModeReasons & POWER_MODE_REASON_CHANGE_DISPLAY) == 0) {
+            Trace.instant(Trace.TRACE_TAG_WINDOW_MANAGER, "EndModeDisplayChange");
+            mPowerManagerInternal.setPowerMode(PowerManagerInternal.MODE_DISPLAY_CHANGE, false);
         }
     }
 
@@ -5751,7 +5781,7 @@
                 case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
                     synchronized (mGlobalLock) {
                         mRetainPowerModeAndTopProcessState = false;
-                        endLaunchPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY);
+                        endPowerMode(POWER_MODE_REASON_UNKNOWN_VISIBILITY);
                         if (mTopApp != null
                                 && mTopProcessState == ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
                             // Restore the scheduling group for sleeping.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 901975b..9fd4720 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2009,7 +2009,7 @@
         }
 
         // End power mode launch before going sleep
-        mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_ALL);
+        mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_ALL);
 
         // Rank task layers to make sure the {@link Task#mLayerRank} is updated.
         mRootWindowContainer.rankTaskLayers();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ca42400..f6fe9b1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3484,7 +3484,7 @@
         final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */,
                 this, this, null /* remoteTransition */, displayChange);
         if (t != null) {
-            mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+            mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
             if (mAsyncRotationController != null) {
                 // Give a chance to update the transform if the current rotation is changed when
                 // some windows haven't finished previous rotation.
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index f0757db..5f4a1c5 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -123,7 +123,7 @@
                 displayChange);
 
         if (t != null) {
-            mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+            mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
             mTransition = t;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ee05e35..b738c1c 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -299,8 +299,7 @@
             // Just to be sure end the launch hint in case the target activity was never launched.
             // However, if we're keeping the activity and making it visible, we can leave it on.
             if (reorderMode != REORDER_KEEP_IN_PLACE) {
-                mService.endLaunchPowerMode(
-                        ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+                mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
             }
 
             // Once the target is shown, prevent spurious background app switches
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ea5c9c2..5533759 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3272,7 +3272,7 @@
             }
         }
         // End power mode launch when idle.
-        mService.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+        mService.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
         return true;
     }
 
@@ -3478,7 +3478,7 @@
                 reason |= ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY;
             }
         }
-        mService.startLaunchPowerMode(reason);
+        mService.startPowerMode(reason);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c92a781..7109137 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -879,7 +879,7 @@
         // It is usually a no-op but make sure that the metric consumer is removed.
         mTransitionMetricsReporter.reportAnimationStart(record.getToken(), 0 /* startTime */);
         // It is a no-op if the transition did not change the display.
-        mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+        mAtm.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
         if (!mPlayingTransitions.contains(record)) {
             Slog.e(TAG, "Trying to finish a non-playing transition " + record);
             return;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3db7765..82452cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6203,7 +6203,7 @@
         mScreenFrozenLock.acquire();
         // Apply launch power mode to reduce screen frozen time because orientation change may
         // relaunch activity and redraw windows. This may also help speed up user switching.
-        mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+        mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
 
         mDisplayFrozen = true;
         mDisplayFreezeTime = SystemClock.elapsedRealtime();
@@ -6345,7 +6345,7 @@
         if (configChanged) {
             displayContent.sendNewConfiguration();
         }
-        mAtmService.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+        mAtmService.endPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
         mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
     }
 
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index c7a71ee..ce28682 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -34,8 +34,11 @@
     ],
     static_libs: [
         "ApexInstallHelper",
+        "android.security.flags-aconfig-java-host",
         "cts-host-utils",
+        "flag-junit-host",
         "frameworks-base-hostutils",
+        "kotlin-test",
         "PackageManagerServiceHostTestsIntentVerifyUtils",
         "block_device_writer_jar",
     ],
@@ -59,6 +62,7 @@
         ":PackageManagerTestAppUsesStaticLibrary",
         ":PackageManagerTestAppVersion1",
         ":PackageManagerTestAppVersion2",
+        ":PackageManagerTestAppVersion2AltKey",
         ":PackageManagerTestAppVersion3",
         ":PackageManagerTestAppVersion3Invalid",
         ":PackageManagerTestAppVersion4",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
new file mode 100644
index 0000000..c490604
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.pm.test
+
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.host.HostFlagsValueProvider
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.io.RandomAccessFile
+import kotlin.test.assertNotNull
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+@RequiresFlagsEnabled(android.security.Flags.FLAG_EXTEND_VB_CHAIN_TO_UPDATED_APK)
+class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
+
+    companion object {
+        private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+        private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+        private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk"
+        private const val VERSION_TWO_ALT_KEY_IDSIG =
+                "PackageManagerTestAppVersion2AltKey.apk.idsig"
+        private const val STRICT_SIGNATURE_CONFIG_PATH =
+                "/system/etc/sysconfig/preinstalled-packages-strict-signature.xml"
+        private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref"
+
+        @get:ClassRule
+        val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+    }
+
+    private val tempFolder = TemporaryFolder()
+    private val preparer: SystemPreparer = SystemPreparer(
+        tempFolder,
+            SystemPreparer.RebootStrategy.FULL,
+        deviceRebootRule
+    ) { this.device }
+    private val productPath =
+            HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
+    private lateinit var originalConfigFile: File
+
+    @Rule
+    @JvmField
+    val checkFlagsRule = HostFlagsValueProvider.createCheckFlagsRule({ getDevice() })
+
+    @Rule
+    @JvmField
+    val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+
+    @Before
+    @After
+    fun removeApk() {
+        device.uninstallPackage(TEST_PKG_NAME)
+    }
+
+    @Before
+    fun backupAndModifySystemFiles() {
+        // Backup
+        device.pullFile(STRICT_SIGNATURE_CONFIG_PATH).also {
+            assertNotNull(it)
+            originalConfigFile = it
+        }
+
+        // Modify to allowlist the target package on device for testing the feature
+        val xml = tempFolder.newFile().apply {
+            val newConfigText = originalConfigFile
+                    .readText()
+                    .replace(
+                        "</config>",
+                            "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>"
+                    )
+            writeText(newConfigText)
+        }
+        device.remountSystemWritable()
+        device.pushFile(xml, STRICT_SIGNATURE_CONFIG_PATH)
+    }
+
+    @After
+    fun restoreSystemFiles() {
+        device.remountSystemWritable()
+        device.pushFile(originalConfigFile, STRICT_SIGNATURE_CONFIG_PATH)
+        // Files pushed via a SystemPreparer are deleted automatically.
+    }
+
+    @Test
+    fun detectApkAndXmlTamperingAtBoot() {
+        // Set up the scenario where both APK and packages.xml are tampered by the attacker.
+        // This is done by booting with the "bad" APK in a system partition, re-installing it to
+        // /data. Then, replace the APK in the system partition with a "good" one.
+        preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString())
+                .reboot()
+
+        // Install the "bad" APK to /data. This will also update package manager's XML records.
+        val versionTwoFile = HostUtils.copyResourceToHostFile(
+            VERSION_TWO_ALT_KEY,
+                tempFolder.newFile()
+        )
+        assertThat(device.installPackage(versionTwoFile, true)).isNull()
+        assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
+                .doesNotContain(productPath.toString())
+
+        // "Restore" the system partition is to a good state with correct APK.
+        preparer.deleteFile(productPath.toString())
+                .pushResourceFile(VERSION_ONE, productPath.toString())
+
+        // Verify that upon the next boot, the system detect the problem and remove the problematic
+        // APK in the /data.
+        preparer.reboot()
+        assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
+                .contains(productPath.toString())
+    }
+
+    @Test
+    fun detectApkTamperingAtBoot() {
+        // Set up the scenario where APK is tampered but not the v4 signature. First, inject a
+        // good APK as a system app.
+        preparer.pushResourceFile(VERSION_TWO_ALT_KEY, productPath.toString())
+                .reboot()
+
+        // Re-install the target APK to /data, with the corresponding .idsig from build time.
+        val versionTwoFile = HostUtils.copyResourceToHostFile(
+            VERSION_TWO_ALT_KEY,
+                tempFolder.newFile()
+        )
+        assertThat(device.installPackage(versionTwoFile, true)).isNull()
+        val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}")
+                .lineSequence()
+                .first()
+                .replace("package:", "")
+        assertThat(baseApkPath).doesNotContain(productPath.toString())
+        preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig")
+
+        // Replace the APK in /data with a tampered version. Restore fs-verity and attributes.
+        RandomAccessFile(versionTwoFile, "rw").use {
+            // Skip the zip local file header to keep it valid. Tamper with the file name field and
+            // beyond, just so that it won't simply fail.
+            it.seek(30)
+            it.writeBytes("tamper")
+        }
+        device.executeShellCommand("touch ${TIMESTAMP_REFERENCE_FILE_PATH} -r $baseApkPath")
+        preparer.pushFile(versionTwoFile, baseApkPath)
+        device.executeShellCommand(
+            "cd ${baseApkPath.replace("base.apk", "")}" +
+                "&& chown system:system base.apk " +
+                "&& /data/local/tmp/fsverity_multilib enable base.apk" +
+                "&& touch base.apk -r ${TIMESTAMP_REFERENCE_FILE_PATH}"
+        )
+
+        // Verify that upon the next boot, the system detect the problem and remove the problematic
+        // APK in the /data.
+        preparer.reboot()
+        assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
+                .contains(productPath.toString())
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index 5cc3371..bee7c40 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -66,3 +66,13 @@
         "src/**/*.kt",
     ],
 }
+
+android_test_helper_app {
+    name: "PackageManagerTestAppVersion2AltKey",
+    manifest: "AndroidManifestVersion2.xml",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    certificate: ":FrameworksServicesTests_keyset_A_cert",
+    v4_signature: true,
+}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 0376376..184c976 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.graphics.FontListParser;
+import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontStyle;
 import android.graphics.fonts.FontUpdateRequest;
@@ -330,7 +331,8 @@
 
             FontConfig.FontFamily family = new FontConfig.FontFamily(
                     Arrays.asList(fooFont, barFont), null,
-                    FontConfig.FontFamily.VARIANT_DEFAULT);
+                    FontConfig.FontFamily.VARIANT_DEFAULT,
+                    FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
             return new FontConfig(Collections.emptyList(),
                     Collections.emptyList(),
                     Collections.singletonList(new FontConfig.NamedFamilyList(
@@ -491,7 +493,8 @@
                     file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
                     0, null, null);
             FontConfig.FontFamily family = new FontConfig.FontFamily(
-                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
+                    FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
             return new FontConfig(
                     Collections.emptyList(),
                     Collections.emptyList(),
@@ -644,7 +647,8 @@
                     file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
                     null);
             FontConfig.FontFamily family = new FontConfig.FontFamily(
-                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT,
+                    FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
             return new FontConfig(Collections.emptyList(), Collections.emptyList(),
                     Collections.singletonList(new FontConfig.NamedFamilyList(
                             Collections.singletonList(family), "sans-serif")), 0, 1);
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
index 3fc0e4f..255cb64 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.inputmethod;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 
 import android.os.LocaleList;
@@ -386,4 +388,10 @@
             assertEquals(availableLocales.get(3), dest.get(0));
         }
     }
+
+    @Test
+    public void testGetLanguageFromLocaleString() {
+        assertThat(LocaleUtils.getLanguageFromLocaleString("en")).isEqualTo("en");
+        assertThat(LocaleUtils.getLanguageFromLocaleString("en-US")).isEqualTo("en");
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
index 6165260..d64cbcd 100644
--- a/services/tests/servicestests/src/com/android/server/timezone/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/timezone/OWNERS
@@ -1,2 +1,2 @@
-# Bug component: 24949
+# Bug component: 847766
 include /services/core/java/com/android/server/timezone/OWNERS
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 72c3ebe..e7ebd7db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -43,6 +43,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.never;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -58,6 +59,7 @@
 import android.graphics.Rect;
 import android.os.Binder;
 import android.os.LocaleList;
+import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.Display;
@@ -380,12 +382,12 @@
         // The top app should not change while sleeping.
         assertEquals(topActivity.app, mAtm.mInternal.getTopApp());
 
-        mAtm.startLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY
+        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY
                 | ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
         assertEquals(ActivityManager.PROCESS_STATE_TOP, mAtm.mInternal.getTopProcessState());
         // Because there is no unknown visibility record, the state will be restored if other
         // reasons are all done.
-        mAtm.endLaunchPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
         assertEquals(ActivityManager.PROCESS_STATE_TOP_SLEEPING,
                 mAtm.mInternal.getTopProcessState());
 
@@ -410,6 +412,37 @@
     }
 
     @Test
+    public void testSetPowerMode() {
+        // Depends on the mocked power manager set in SystemServicesTestRule#setUpLocalServices.
+        mAtm.onInitPowerManagement();
+
+        // Apply different power modes according to the reasons.
+        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+        verify(mWm.mPowerManagerInternal).setPowerMode(
+                PowerManagerInternal.MODE_LAUNCH, true);
+        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY);
+        verify(mWm.mPowerManagerInternal).setPowerMode(
+                PowerManagerInternal.MODE_DISPLAY_CHANGE, true);
+
+        // If there is unknown visibility launching app, the launch power mode won't be canceled
+        // even if REASON_START_ACTIVITY is cleared.
+        mAtm.startPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_UNKNOWN_VISIBILITY);
+        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(mock(ActivityRecord.class));
+        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+        verify(mWm.mPowerManagerInternal, never()).setPowerMode(
+                PowerManagerInternal.MODE_LAUNCH, false);
+
+        mDisplayContent.mUnknownAppVisibilityController.clear();
+        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_START_ACTIVITY);
+        verify(mWm.mPowerManagerInternal).setPowerMode(
+                PowerManagerInternal.MODE_LAUNCH, false);
+
+        mAtm.endPowerMode(ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY);
+        verify(mWm.mPowerManagerInternal).setPowerMode(
+                PowerManagerInternal.MODE_DISPLAY_CHANGE, false);
+    }
+
+    @Test
     public void testSupportsMultiWindow_resizable() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 75716b9..241f7d8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1081,6 +1081,7 @@
         makeWindowVisible(windows);
         mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
         mDisplayContent.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
+        mDisplayContent.mTransitionController.setSyncEngine(createTestBLASTSyncEngine());
         final TestTransitionPlayer player = registerTestTransitionPlayer();
 
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 1ddb5c0..18c960e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2607,11 +2607,12 @@
         @Override
         public void reportChooserSelection(@NonNull String packageName, int userId,
                 @NonNull String contentType, String[] annotations, @NonNull String action) {
-            // A valid package name, content type, and action must be provided for these events
-            Objects.requireNonNull(packageName);
-            Objects.requireNonNull(contentType);
-            Objects.requireNonNull(action);
-            if (contentType.isBlank() || action.isBlank()) {
+            if (packageName == null) {
+                throw new IllegalArgumentException("Package selection must not be null.");
+            }
+            // A valid contentType and action must be provided for chooser selection events.
+            if (contentType == null || contentType.isBlank()
+                    || action == null || action.isBlank()) {
                 return;
             }
 
diff --git a/tests/Input/src/com/android/server/input/InputShellCommandTest.java b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
new file mode 100644
index 0000000..f4845a5
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/InputShellCommandTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import static android.view.InputDevice.SOURCE_MOUSE;
+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 static android.view.MotionEvent.AXIS_X;
+import static android.view.MotionEvent.AXIS_Y;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.os.Binder;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+/**
+ * Build/Install/Run:
+ * atest InputShellCommandTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class InputShellCommandTest {
+    private TestInputEventInjector mInputEventInjector = new TestInputEventInjector();
+
+    private InputShellCommand mCommand;
+
+    @Before
+    public void setUp() throws Exception {
+        mCommand = new InputShellCommand(mInputEventInjector);
+    }
+
+    @Test
+    public void testScroll_withPointerSource_noAxisOption() {
+        runCommand("mouse scroll 2 -3");
+
+        MotionEvent event = (MotionEvent) getSingleInjectedInputEvent();
+
+        assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL);
+        assertAxisValues(event, Map.of(AXIS_X, 2f, AXIS_Y, -3f));
+    }
+
+    @Test
+    public void testScroll_withPointerSource_withScrollAxisOptions() {
+        runCommand("mouse scroll 1 -2 --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4");
+
+        MotionEvent event = (MotionEvent) getSingleInjectedInputEvent();
+
+        assertSourceAndAction(event, SOURCE_MOUSE, ACTION_SCROLL);
+        assertAxisValues(
+                event,
+                Map.of(
+                        AXIS_X, 1f,
+                        AXIS_Y, -2f,
+                        AXIS_HSCROLL, 3f,
+                        AXIS_VSCROLL, 1.7f,
+                        AXIS_SCROLL, -4f));
+    }
+
+    @Test
+    public void testScroll_withNonPointerSource_noAxisOption() {
+        runCommand("rotaryencoder scroll");
+
+        MotionEvent event = (MotionEvent) getSingleInjectedInputEvent();
+
+        assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL);
+    }
+
+    @Test
+    public void testScroll_withNonPointerSource_withScrollAxisOptions() {
+        runCommand("rotaryencoder scroll --axis HSCROLL,3 --axis VSCROLL,1.7 --axis SCROLL,-4");
+
+        MotionEvent event = (MotionEvent) getSingleInjectedInputEvent();
+
+        assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL);
+        assertAxisValues(event, Map.of(AXIS_HSCROLL, 3f, AXIS_VSCROLL, 1.7f, AXIS_SCROLL, -4f));
+    }
+
+    @Test
+    public void testDefaultScrollSource() {
+        runCommand("scroll --axis SCROLL,-4");
+
+        MotionEvent event = (MotionEvent) getSingleInjectedInputEvent();
+
+        assertSourceAndAction(event, SOURCE_ROTARY_ENCODER, ACTION_SCROLL);
+        assertAxisValues(event, Map.of(AXIS_SCROLL, -4f));
+    }
+
+    @Test
+    public void testInvalidScrollCommands() {
+        runCommand("scroll --sdaxis SCROLL,-4"); // invalid option
+        runCommand("scroll --axis MYAXIS,-4"); // invalid axis
+        runCommand("scroll --AXIS SCROLL,-4"); // invalid axis option key
+        runCommand("scroll --axis SCROLL,-4abc"); // invalid axis value
+
+        assertThat(mInputEventInjector.mInjectedEvents).isEmpty();
+    }
+
+    private InputEvent getSingleInjectedInputEvent() {
+        assertThat(mInputEventInjector.mInjectedEvents).hasSize(1);
+        return mInputEventInjector.mInjectedEvents.get(0);
+    }
+
+    private void assertSourceAndAction(MotionEvent event, int source, int action) {
+        assertThat(event.getSource()).isEqualTo(source);
+        assertThat(event.getAction()).isEqualTo(action);
+    }
+
+    private void assertAxisValues(MotionEvent event, Map<Integer, Float> expectedValues) {
+        for (var entry : expectedValues.entrySet()) {
+            final int axis = entry.getKey();
+            final float expectedValue = entry.getValue();
+            final float axisValue = event.getAxisValue(axis);
+            assertWithMessage(
+                    String.format(
+                            "Expected [%f], found [%f] for axis %s",
+                            expectedValue,
+                            axisValue,
+                            MotionEvent.axisToString(axis)))
+                    .that(axisValue).isEqualTo(expectedValue);
+        }
+    }
+
+    private void runCommand(String cmd) {
+        mCommand.exec(
+                new Binder(), new FileDescriptor(), new FileDescriptor(), new FileDescriptor(),
+                cmd.split(" ") /* args */);
+    }
+
+    private static class TestInputEventInjector implements BiConsumer<InputEvent, Integer> {
+        List<InputEvent> mInjectedEvents = new ArrayList<>();
+
+        @Override
+        public void accept(InputEvent event, Integer injectMode) {
+            mInjectedEvents.add(event);
+        }
+    }
+}